PageRenderTime 42ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/NAudio/MediaFoundation/MediaFoundationTransform.cs

#
C# | 286 lines | 189 code | 29 blank | 68 comment | 21 complexity | 07f0512313f226f669f76356703a2577 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. using System;
  2. using System.Runtime.InteropServices;
  3. using NAudio.Utils;
  4. using NAudio.Wave;
  5. namespace NAudio.MediaFoundation
  6. {
  7. /// <summary>
  8. /// An abstract base class for simplifying working with Media Foundation Transforms
  9. /// You need to override the method that actually creates and configures the transform
  10. /// </summary>
  11. public abstract class MediaFoundationTransform : IWaveProvider, IDisposable
  12. {
  13. /// <summary>
  14. /// The Source Provider
  15. /// </summary>
  16. protected readonly IWaveProvider sourceProvider;
  17. /// <summary>
  18. /// The Output WaveFormat
  19. /// </summary>
  20. protected readonly WaveFormat outputWaveFormat;
  21. private readonly byte[] sourceBuffer;
  22. private byte[] outputBuffer;
  23. private int outputBufferOffset;
  24. private int outputBufferCount;
  25. private IMFTransform transform;
  26. private bool disposed;
  27. private long inputPosition; // in ref-time, so we can timestamp the input samples
  28. private long outputPosition; // also in ref-time
  29. private bool initializedForStreaming;
  30. /// <summary>
  31. /// Constructs a new MediaFoundationTransform wrapper
  32. /// Will read one second at a time
  33. /// </summary>
  34. /// <param name="sourceProvider">The source provider for input data to the transform</param>
  35. /// <param name="outputFormat">The desired output format</param>
  36. public MediaFoundationTransform(IWaveProvider sourceProvider, WaveFormat outputFormat)
  37. {
  38. this.outputWaveFormat = outputFormat;
  39. this.sourceProvider = sourceProvider;
  40. sourceBuffer = new byte[sourceProvider.WaveFormat.AverageBytesPerSecond];
  41. outputBuffer = new byte[outputWaveFormat.AverageBytesPerSecond + outputWaveFormat.BlockAlign]; // we will grow this buffer if needed, but try to make something big enough
  42. }
  43. private void InitializeTransformForStreaming()
  44. {
  45. transform.ProcessMessage(MFT_MESSAGE_TYPE.MFT_MESSAGE_COMMAND_FLUSH, IntPtr.Zero);
  46. transform.ProcessMessage(MFT_MESSAGE_TYPE.MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, IntPtr.Zero);
  47. transform.ProcessMessage(MFT_MESSAGE_TYPE.MFT_MESSAGE_NOTIFY_START_OF_STREAM, IntPtr.Zero);
  48. initializedForStreaming = true;
  49. }
  50. /// <summary>
  51. /// To be implemented by overriding classes. Create the transform object, set up its input and output types,
  52. /// and configure any custom properties in here
  53. /// </summary>
  54. /// <returns>An object implementing IMFTrasform</returns>
  55. protected abstract IMFTransform CreateTransform();
  56. /// <summary>
  57. /// Disposes this MediaFoundation transform
  58. /// </summary>
  59. protected virtual void Dispose(bool disposing)
  60. {
  61. if (transform != null)
  62. {
  63. Marshal.ReleaseComObject(transform);
  64. }
  65. }
  66. /// <summary>
  67. /// Disposes this Media Foundation Transform
  68. /// </summary>
  69. public void Dispose()
  70. {
  71. if (!disposed)
  72. {
  73. disposed = true;
  74. Dispose(true);
  75. GC.SuppressFinalize(this);
  76. }
  77. }
  78. /// <summary>
  79. /// Destructor
  80. /// </summary>
  81. ~MediaFoundationTransform()
  82. {
  83. Dispose(false);
  84. }
  85. /// <summary>
  86. /// The output WaveFormat of this Media Foundation Transform
  87. /// </summary>
  88. public WaveFormat WaveFormat { get { return outputWaveFormat; } }
  89. /// <summary>
  90. /// Reads data out of the source, passing it through the transform
  91. /// </summary>
  92. /// <param name="buffer">Output buffer</param>
  93. /// <param name="offset">Offset within buffer to write to</param>
  94. /// <param name="count">Desired byte count</param>
  95. /// <returns>Number of bytes read</returns>
  96. public int Read(byte[] buffer, int offset, int count)
  97. {
  98. if (transform == null)
  99. {
  100. transform = CreateTransform();
  101. InitializeTransformForStreaming();
  102. }
  103. // strategy will be to always read 1 second from the source, and give it to the resampler
  104. int bytesWritten = 0;
  105. // read in any leftovers from last time
  106. if (outputBufferCount > 0)
  107. {
  108. bytesWritten += ReadFromOutputBuffer(buffer, offset, count - bytesWritten);
  109. }
  110. while (bytesWritten < count)
  111. {
  112. var sample = ReadFromSource();
  113. if (sample == null) // reached the end of our input
  114. {
  115. // be good citizens and send some end messages:
  116. EndStreamAndDrain();
  117. // resampler might have given us a little bit more to return
  118. bytesWritten += ReadFromOutputBuffer(buffer, offset + bytesWritten, count - bytesWritten);
  119. break;
  120. }
  121. // might need to resurrect the stream if the user has read all the way to the end,
  122. // and then repositioned the input backwards
  123. if (!initializedForStreaming)
  124. {
  125. InitializeTransformForStreaming();
  126. }
  127. // give the input to the resampler
  128. // can get MF_E_NOTACCEPTING if we didn't drain the buffer properly
  129. transform.ProcessInput(0, sample, 0);
  130. Marshal.ReleaseComObject(sample);
  131. int readFromTransform;
  132. // n.b. in theory we ought to loop here, although we'd need to be careful as the next time into ReadFromTransform there could
  133. // still be some leftover bytes in outputBuffer, which would get overwritten. Only introduce this if we find a transform that
  134. // needs it. For most transforms, alternating read/write should be OK
  135. //do
  136. //{
  137. // keep reading from transform
  138. readFromTransform = ReadFromTransform();
  139. bytesWritten += ReadFromOutputBuffer(buffer, offset + bytesWritten, count - bytesWritten);
  140. //} while (readFromTransform > 0);
  141. }
  142. return bytesWritten;
  143. }
  144. private void EndStreamAndDrain()
  145. {
  146. transform.ProcessMessage(MFT_MESSAGE_TYPE.MFT_MESSAGE_NOTIFY_END_OF_STREAM, IntPtr.Zero);
  147. transform.ProcessMessage(MFT_MESSAGE_TYPE.MFT_MESSAGE_COMMAND_DRAIN, IntPtr.Zero);
  148. int read;
  149. do
  150. {
  151. read = ReadFromTransform();
  152. } while (read > 0);
  153. outputBufferCount = 0;
  154. outputBufferOffset = 0;
  155. inputPosition = 0;
  156. outputPosition = 0;
  157. transform.ProcessMessage(MFT_MESSAGE_TYPE.MFT_MESSAGE_NOTIFY_END_STREAMING, IntPtr.Zero);
  158. initializedForStreaming = false;
  159. }
  160. /// <summary>
  161. /// Attempts to read from the transform
  162. /// Some useful info here:
  163. /// http://msdn.microsoft.com/en-gb/library/windows/desktop/aa965264%28v=vs.85%29.aspx#process_data
  164. /// </summary>
  165. /// <returns></returns>
  166. private int ReadFromTransform()
  167. {
  168. var outputDataBuffer = new MFT_OUTPUT_DATA_BUFFER[1];
  169. // we have to create our own for
  170. var sample = MediaFoundationApi.CreateSample();
  171. var pBuffer = MediaFoundationApi.CreateMemoryBuffer(outputBuffer.Length);
  172. sample.AddBuffer(pBuffer);
  173. sample.SetSampleTime(outputPosition); // hopefully this is not needed
  174. outputDataBuffer[0].pSample = sample;
  175. _MFT_PROCESS_OUTPUT_STATUS status;
  176. var hr = transform.ProcessOutput(_MFT_PROCESS_OUTPUT_FLAGS.None,
  177. 1, outputDataBuffer, out status);
  178. if (hr == MediaFoundationErrors.MF_E_TRANSFORM_NEED_MORE_INPUT)
  179. {
  180. Marshal.ReleaseComObject(pBuffer);
  181. Marshal.ReleaseComObject(sample);
  182. // nothing to read
  183. return 0;
  184. }
  185. else if (hr != 0)
  186. {
  187. Marshal.ThrowExceptionForHR(hr);
  188. }
  189. IMFMediaBuffer outputMediaBuffer;
  190. outputDataBuffer[0].pSample.ConvertToContiguousBuffer(out outputMediaBuffer);
  191. IntPtr pOutputBuffer;
  192. int outputBufferLength;
  193. int maxSize;
  194. outputMediaBuffer.Lock(out pOutputBuffer, out maxSize, out outputBufferLength);
  195. outputBuffer = BufferHelpers.Ensure(outputBuffer, outputBufferLength);
  196. Marshal.Copy(pOutputBuffer, outputBuffer, 0, outputBufferLength);
  197. outputBufferOffset = 0;
  198. outputBufferCount = outputBufferLength;
  199. outputMediaBuffer.Unlock();
  200. outputPosition += BytesToNsPosition(outputBufferCount, WaveFormat); // hopefully not needed
  201. Marshal.ReleaseComObject(pBuffer);
  202. Marshal.ReleaseComObject(sample);
  203. Marshal.ReleaseComObject(outputMediaBuffer);
  204. return outputBufferLength;
  205. }
  206. private static long BytesToNsPosition(int bytes, WaveFormat waveFormat)
  207. {
  208. long nsPosition = (10000000L * bytes) / waveFormat.AverageBytesPerSecond;
  209. return nsPosition;
  210. }
  211. private IMFSample ReadFromSource()
  212. {
  213. // we always read a full second
  214. int bytesRead = sourceProvider.Read(sourceBuffer, 0, sourceBuffer.Length);
  215. if (bytesRead == 0) return null;
  216. var mediaBuffer = MediaFoundationApi.CreateMemoryBuffer(bytesRead);
  217. IntPtr pBuffer;
  218. int maxLength, currentLength;
  219. mediaBuffer.Lock(out pBuffer, out maxLength, out currentLength);
  220. Marshal.Copy(sourceBuffer, 0, pBuffer, bytesRead);
  221. mediaBuffer.Unlock();
  222. mediaBuffer.SetCurrentLength(bytesRead);
  223. var sample = MediaFoundationApi.CreateSample();
  224. sample.AddBuffer(mediaBuffer);
  225. // we'll set the time, I don't think it is needed for Resampler, but other MFTs might need it
  226. sample.SetSampleTime(inputPosition);
  227. long duration = BytesToNsPosition(bytesRead, sourceProvider.WaveFormat);
  228. sample.SetSampleDuration(duration);
  229. inputPosition += duration;
  230. Marshal.ReleaseComObject(mediaBuffer);
  231. return sample;
  232. }
  233. private int ReadFromOutputBuffer(byte[] buffer, int offset, int needed)
  234. {
  235. int bytesFromOutputBuffer = Math.Min(needed, outputBufferCount);
  236. Array.Copy(outputBuffer, outputBufferOffset, buffer, offset, bytesFromOutputBuffer);
  237. outputBufferOffset += bytesFromOutputBuffer;
  238. outputBufferCount -= bytesFromOutputBuffer;
  239. if (outputBufferCount == 0)
  240. {
  241. outputBufferOffset = 0;
  242. }
  243. return bytesFromOutputBuffer;
  244. }
  245. /// <summary>
  246. /// Indicate that the source has been repositioned and completely drain out the transforms buffers
  247. /// </summary>
  248. public void Reposition()
  249. {
  250. if (initializedForStreaming)
  251. {
  252. EndStreamAndDrain();
  253. InitializeTransformForStreaming();
  254. }
  255. }
  256. }
  257. }