// Copyright (c) 2010-2011 SharpDX - Alexandre Mutel // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Collections.Generic; using System.IO; using SharpDX.Multimedia; #if STORE_APP using Windows.Storage.Streams; #endif namespace SharpDX.MediaFoundation { /// /// Decoder from compressed audio (mp3, wma...etc.) to PCM. /// /// /// This class was developed following the "Tutorial: Decoding Audio" /// public class AudioDecoder : DisposeBase { private SourceReader sourceReader; private SourceReader nextSourceReader; private Sample currentSample; private MediaBuffer currentBuffer; private readonly object sourceReaderLock = new object(); /// /// Initializes a new instance of the class. /// public AudioDecoder() { // Make sure that the MediaEngine is initialized. MediaManager.Startup(); } /// /// Initializes a new instance of the class. /// /// The stream to read the compressed audio. public AudioDecoder(Stream stream) { // Make sure that the MediaEngine is initialized. MediaManager.Startup(); SetSourceStream(stream); } /// /// Gets or sets the source stream. See remarks. /// /// The source. /// /// The source must be set before calling /// public void SetSourceStream(Stream value) { lock (sourceReaderLock) { // If the nextSourceReader is not null if (nextSourceReader != null) nextSourceReader.Dispose(); nextSourceReader = new SourceReader(value); Initialize(nextSourceReader); } } #if STORE_APP /// /// Initializes a new instance of the class. /// /// The stream to read the compressed audio. public AudioDecoder(IRandomAccessStream stream) { // Make sure that the MediaEngine is initialized. MediaManager.Startup(); SetSourceStream(stream); } /// /// Gets or sets the source stream. See remarks. /// /// The source. /// /// The source must be set before calling /// public void SetSourceStream(IRandomAccessStream value) { lock (sourceReaderLock) { // If the nextSourceReader is not null if (nextSourceReader != null) nextSourceReader.Dispose(); nextSourceReader = new SourceReader(value); Initialize(nextSourceReader); } } #endif /// /// Gets the total duration in seconds. /// /// The duration. public TimeSpan Duration { get; private set; } /// /// Gets the PCM wave format output by this decoder. /// /// The wave format. public WaveFormat WaveFormat { get; private set; } /// /// Gets the decoded PCM samples. See remarks. /// /// An enumerator of pointer to PCM decoded data with the same format as returned by . /// /// This method is only working as a single enumerator at a time. /// /// /// The must be set before calling /// public IEnumerable GetSamples() { return GetSamples(new TimeSpan()); } /// /// Gets the decoded PCM samples. See remarks. /// /// The starting position in seconds. /// An enumerator of pointer to PCM decoded data with the same format as returned by . /// /// This method is only working as a single enumerator at a time. /// The must be set before calling /// public IEnumerable GetSamples(TimeSpan startingPositionInSeconds) { // A new reader is setup, so initialize it. lock (sourceReaderLock) { // If the reader was changed if (nextSourceReader != null) { if (sourceReader != null) sourceReader.Dispose(); sourceReader = nextSourceReader; nextSourceReader = null; } } // Make sure that any prior call CleanupAndDispose(); CheckIfDisposed(); // Set the position sourceReader.SetCurrentPosition((long)(startingPositionInSeconds.Ticks)); while (true) { int streamIndex; SourceReaderFlags flags; long time; CheckIfDisposed(); using (currentSample = sourceReader.ReadSample(SourceReaderIndex.FirstAudioStream, SourceReaderControlFlags.None, out streamIndex, out flags, out time)) { if ((flags & SourceReaderFlags.Endofstream) != 0) break; CheckIfDisposed(); using (currentBuffer = currentSample.ConvertToContiguousBuffer()) { int bufferMaxLength; int bufferCurrentLength; CheckIfDisposed(); var ptr = currentBuffer.Lock(out bufferMaxLength, out bufferCurrentLength); yield return new DataPointer(ptr, bufferCurrentLength); // Warning, because the yield could never return here, currentBuffer and currentSample should be disposed when disposing this object or when // calling it again on the GetSamples method. // In case a Dispose occurred while decoding if (currentBuffer == null) break; currentBuffer.Unlock(); } } } // They have been disposed, so we can just clear them currentBuffer = null; currentSample = null; } private void CleanupAndDispose() { if (currentBuffer != null) { currentBuffer.Unlock(); currentBuffer.Dispose(); currentBuffer = null; } if (currentSample != null) { currentSample.Dispose(); currentSample = null; } } private void CheckIfDisposed() { if (IsDisposed) throw new InvalidOperationException("This instance is disposed while enumerating the samples."); } protected override void Dispose(bool disposeManagedResources) { CleanupAndDispose(); if (nextSourceReader != null) { nextSourceReader.Dispose(); nextSourceReader = null; } if (sourceReader != null) { sourceReader.Dispose(); sourceReader = null; } } private void Initialize(SourceReader reader) { // Invalidate selection for all streams reader.SetStreamSelection(SourceReaderIndex.AllStreams, false); // Select only audio stream reader.SetStreamSelection(SourceReaderIndex.FirstAudioStream, true); // Get the media type for the current stream. using (var mediaType = reader.GetNativeMediaType(SourceReaderIndex.FirstAudioStream, 0)) { var majorType = mediaType.Get(MediaTypeAttributeKeys.MajorType); if (majorType != MediaTypeGuids.Audio) throw new InvalidOperationException("Input stream doesn't contain an audio stream."); } // Set the type on the source reader to use PCM using (var partialType = new MediaType()) { partialType.Set(MediaTypeAttributeKeys.MajorType, MediaTypeGuids.Audio); partialType.Set(MediaTypeAttributeKeys.Subtype, AudioFormatGuids.Pcm); reader.SetCurrentMediaType(SourceReaderIndex.FirstAudioStream, partialType); } // Retrieve back the real media type using (var realMediaType = reader.GetCurrentMediaType(SourceReaderIndex.FirstAudioStream)) { int sizeRef; WaveFormat = realMediaType.ExtracttWaveFormat(out sizeRef); } Duration = new TimeSpan(reader.GetPresentationAttribute(SourceReaderIndex.MediaSource, PresentationDescriptionAttributeKeys.Duration)); } } }