/// <summary> /// Use Source Reader to allocate a sample /// </summary> /// <param name="sourceReader">The Source Reader</param> /// <param name="dwStreamIndex">The stream to pull data from</param> /// <param name="dwControlFlags">A bitwise OR of zero or more flags from the MF_SOURCE_READER_CONTROL_FLAG enumeration</param> /// <param name="pdwActualStreamIndex">Receives the zero-based index of the stream</param> /// <param name="pdwStreamFlags">Receives a bitwise OR of zero or more flags from the MF_SOURCE_READER_FLAG enumeration</param> /// <param name="pllTimestamp">Receives the time stamp of the sample, or the time of the stream event indicated in pdwStreamFlags. The time is given in 100-nanosecond units</param> public void ReadSampleFrom( IMFSourceReader sourceReader, uint dwStreamIndex, uint dwControlFlags, out uint pdwActualStreamIndex, out uint pdwStreamFlags, out ulong pllTimestamp) { // Once a sample is read avoid reading more samples if (this.read) { throw new InvalidOperationException("Already holding a sample"); } sourceReader.ReadSample(dwStreamIndex, dwControlFlags, out pdwActualStreamIndex, out pdwStreamFlags, out pllTimestamp, out this.sample); if (this.sample != null) { // Get the size of the media sample in bytes IMFMediaBuffer buffer = null; this.sample.GetBufferByIndex(0, out buffer); buffer.GetCurrentLength(out this.bufferSize); // Add the memory pressure of unmanaged memory to improve the garbage collector performance GC.AddMemoryPressure(this.bufferSize); } this.read = true; }
/// <summary> /// Reads from this wave stream /// </summary> /// <param name="buffer">Buffer to read into</param> /// <param name="offset">Offset in buffer</param> /// <param name="count">Bytes required</param> /// <returns>Number of bytes read; 0 indicates end of stream</returns> public override int Read(byte[] buffer, int offset, int count) { if (pReader == null) { pReader = CreateReader(settings); } if (repositionTo != -1) { Reposition(repositionTo); } int bytesWritten = 0; // read in any leftovers from last time if (decoderOutputCount > 0) { bytesWritten += ReadFromDecoderBuffer(buffer, offset, count - bytesWritten); } while (bytesWritten < count) { IMFSample pSample; MF_SOURCE_READER_FLAG dwFlags; ulong timestamp; int actualStreamIndex; pReader.ReadSample(MediaFoundationInterop.MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, out actualStreamIndex, out dwFlags, out timestamp, out pSample); if ((dwFlags & MF_SOURCE_READER_FLAG.MF_SOURCE_READERF_ENDOFSTREAM) != 0) { // reached the end of the stream break; } else if ((dwFlags & MF_SOURCE_READER_FLAG.MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) != 0) { Console.WriteLine("SAMPLE SETTINGS CHANGED!"); waveFormat = GetCurrentWaveFormat(pReader, false, 0, 0, 0); OnWaveFormatChanged(); // carry on, but user must handle the change of format } else if (dwFlags != 0) { throw new InvalidOperationException(String.Format("MediaFoundationReadError {0}", dwFlags)); } IMFMediaBuffer pBuffer; pSample.ConvertToContiguousBuffer(out pBuffer); IntPtr pAudioData; int cbBuffer; int pcbMaxLength; pBuffer.Lock(out pAudioData, out pcbMaxLength, out cbBuffer); EnsureBuffer(cbBuffer); Marshal.Copy(pAudioData, decoderOutputBuffer, 0, cbBuffer); decoderOutputOffset = 0; decoderOutputCount = cbBuffer; bytesWritten += ReadFromDecoderBuffer(buffer, offset + bytesWritten, count - bytesWritten); pBuffer.Unlock(); Marshal.ReleaseComObject(pBuffer); Marshal.ReleaseComObject(pSample); } position += bytesWritten; return(bytesWritten); }
/// <summary> /// Reads the next sample from the media source. /// </summary> /// <param name="sourceReader">A valid IMFSourceReader instance.</param></param></param> /// <param name="streamIndex">The stream to pull data from.</param> /// <param name="controlFlags">One or more members of the MF_SOURCE_READER_CONTROL_FLAG enumeration.</param> /// <param name="actualStreamIndex">Receives the zero-based index of the stream.</param> /// <param name="streamFlags">Receives one or more members of the MF_SOURCE_READER_FLAG enumeration.</param> /// <param name="timestamp">Receives the time stamp of the sample, or the time of the stream event indicated in <paramref name="streamFlags"/>.</param> /// <param name="sample">Receives an instance of the IMFSample interface or the value null.</param> /// <returns>If this function succeeds, it returns the S_OK member. Otherwise, it returns another HResult's member that describe the error.</returns> public static HResult ReadSample(this IMFSourceReader sourceReader, SourceReaderStreams streamIndex, MF_SOURCE_READER_CONTROL_FLAG controlFlags, out int actualStreamIndex, out MF_SOURCE_READER_FLAG streamFlags, out TimeSpan timestamp, out IMFSample sample) { if (sourceReader == null) { throw new ArgumentNullException("sourceReader"); } long tmp = 0; HResult hr = sourceReader.ReadSample((int)streamIndex, controlFlags, out actualStreamIndex, out streamFlags, out tmp, out sample); timestamp = hr.Succeeded() ? TimeSpan.FromTicks(tmp) : default(TimeSpan); return(hr); }
private static byte[] ReadSample(IMFSourceReader reader, int inputStreamIndex, out int streamIndex, out long timestamp) { Marshal.ThrowExceptionForHR((int)reader.ReadSample(inputStreamIndex, 0, out streamIndex, out MF_SOURCE_READER_FLAG readFlag, out timestamp, out IMFSample sample)); try { if (readFlag == MF_SOURCE_READER_FLAG.EndOfStream) { return(null); } Marshal.ThrowExceptionForHR((int)sample.ConvertToContiguousBuffer(out IMFMediaBuffer buffer)); try { Marshal.ThrowExceptionForHR((int)buffer.Lock(out IntPtr pData, out int maxLength, out int currentLength)); try { byte[] bytes = new byte[currentLength]; Marshal.Copy(pData, bytes, 0, bytes.Length); return(bytes); } finally { Marshal.ThrowExceptionForHR((int)buffer.Unlock()); } } finally { Marshal.ReleaseComObject(buffer); } } finally { if (sample != null) { Marshal.ReleaseComObject(sample); } } }
/// <summary> /// Reads from this wave stream /// </summary> /// <param name="buffer">Buffer to read into</param> /// <param name="offset">Offset in buffer</param> /// <param name="count">Bytes required</param> /// <returns>Number of bytes read; 0 indicates end of stream</returns> public override int Read(byte[] buffer, int offset, int count) { if (pReader == null) { pReader = CreateReader(settings); } if (repositionTo != -1) { Reposition(repositionTo); } int bytesWritten = 0; // read in any leftovers from last time if (decoderOutputCount > 0) { bytesWritten += ReadFromDecoderBuffer(buffer, offset, count - bytesWritten); } while (bytesWritten < count) { IMFSample pSample; int dwFlags; ulong timestamp; int actualStreamIndex; pReader.ReadSample(MediaFoundationInterop.MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, out actualStreamIndex, out dwFlags, out timestamp, out pSample); if (dwFlags != 0) { // reached the end of the stream or media type changed break; }/* * if (dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) * { * printf("Type change - not supported by WAVE file format.\n"); * break; * } * if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM) * { * printf("End of input file.\n"); * break; * }*/ IMFMediaBuffer pBuffer; pSample.ConvertToContiguousBuffer(out pBuffer); IntPtr pAudioData = IntPtr.Zero; int cbBuffer; int pcbMaxLength; pBuffer.Lock(out pAudioData, out pcbMaxLength, out cbBuffer); EnsureBuffer(cbBuffer); Marshal.Copy(pAudioData, decoderOutputBuffer, 0, cbBuffer); decoderOutputOffset = 0; decoderOutputCount = cbBuffer; bytesWritten += ReadFromDecoderBuffer(buffer, offset + bytesWritten, count - bytesWritten); pBuffer.Unlock(); Marshal.ReleaseComObject(pBuffer); Marshal.ReleaseComObject(pSample); } position += bytesWritten; return(bytesWritten); }
/// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+= /// <summary> /// Does everything to copy a file. Opens the Source Reader and Sink Writer /// configures the streams and then, because a Synchronous version /// of the Source Reader is used, we sit in a loop and perform the copy. /// /// Any errors here simply throw an exception and must be trapped elsewhere /// /// Note that because this code is intended for demo purposes, it has been /// kept very simple and linear. Most of the things that could have been /// refactored in a common procedure (audio and video streams) are simply /// written out in duplicate in order to make it obvious what is going on. /// </summary> /// <param name="sourceFileName">the source file name</param> /// <param name="outputFileName">the name of the output file</param> /// <history> /// 01 Nov 18 Cynic - Originally Written /// </history> public void CopyFile(string sourceFileName, string outputFileName) { HResult hr; int sinkWriterOutputVideoStreamId = -1; int videoSamplesProcessed = 0; bool videoStreamIsAtEOS = false; int sourceReaderVideoStreamId = -1; int sinkWriterOutputAudioStreamId = -1; int audioSamplesProcessed = 0; bool audioStreamIsAtEOS = false; int sourceReaderAudioStreamId = -1; // not keen on endless loops. This is the maximum number // of streams we will check in the source reader. const int MAX_SOURCEREADER_STREAMS = 100; // create the SourceReader sourceReader = TantaWMFUtils.CreateSourceReaderSyncFromFile(sourceFileName, DEFAULT_ALLOW_HARDWARE_TRANSFORMS); if (sourceReader == null) { // we failed throw new Exception("CopyFile: Failed to create SourceReader, Nothing will work."); } // create the SinkWriter sinkWriter = TantaWMFUtils.CreateSinkWriterFromFile(outputFileName, DEFAULT_ALLOW_HARDWARE_TRANSFORMS); if (sinkWriter == null) { // we failed throw new Exception("CopyFile: Failed to create Sink Writer, Nothing will work."); } // find the first audio and video stream and identify the default Media Type // they are using. We could look into the streams and enumerate all of the // types on offer and choose one from the list - but for a copy operation // the default will be quite suitable. sourceReaderNativeVideoMediaType = null; sourceReaderNativeAudioMediaType = null; for (int streamIndex = 0; streamIndex < MAX_SOURCEREADER_STREAMS; streamIndex++) { IMFMediaType workingType = null; Guid guidMajorType = Guid.Empty; // if we have found both the video and audio types (and their stream ids) leave now if ((sourceReaderNativeVideoMediaType != null) && (sourceReaderNativeAudioMediaType != null)) { break; } hr = sourceReader.GetNativeMediaType(streamIndex, 0, out workingType); if (hr == HResult.MF_E_NO_MORE_TYPES) { break; } if (hr == HResult.MF_E_INVALIDSTREAMNUMBER) { break; } if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: failed on call to GetNativeMediaType, retVal=" + hr.ToString()); } if (workingType == null) { // we failed throw new Exception("CopyFile: failed on call to GetNativeMediaType, workingType == null"); } // what major type does this stream have? hr = workingType.GetMajorType(out guidMajorType); if (hr != HResult.S_OK) { throw new Exception("CopyFile: call to workingType.GetMajorType failed. Err=" + hr.ToString()); } if (guidMajorType == null) { throw new Exception("CopyFile: call to workingType.GetMajorType failed. guidMajorType == null"); } // test for video or audio (there can be others) if ((guidMajorType == MFMediaType.Video) && (sourceReaderNativeVideoMediaType == null)) { // this stream represents a video type sourceReaderNativeVideoMediaType = workingType; sourceReaderVideoStreamId = streamIndex; // the sourceReaderNativeVideoMediaType will be released elsewhere continue; } else if ((guidMajorType == MFMediaType.Audio) && (sourceReaderNativeAudioMediaType == null)) { // this stream represents a audio type sourceReaderNativeAudioMediaType = workingType; sourceReaderAudioStreamId = streamIndex; // the sourceReaderNativeAudioMediaType will be released elsewhere continue; } // if we get here release the type - we do not use it if (workingType != null) { Marshal.ReleaseComObject(workingType); workingType = null; } } // at this point we expect we can have a native video or a native audio media type // or both, but not neither. if we don't we cannot carry on if ((sourceReaderNativeVideoMediaType == null) && (sourceReaderNativeAudioMediaType == null)) { // we failed throw new Exception("CopyFile: failed on call to GetNativeMediaType, sourceReaderNativeVideoMediaType == null"); } // if we have a video stream in the source file we now set it up if (sourceReaderNativeVideoMediaType != null) { // set the media type on the reader - this is the media type it will output hr = sourceReader.SetCurrentMediaType(sourceReaderVideoStreamId, null, sourceReaderNativeVideoMediaType); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: failed on call to SetCurrentMediaType(v), retVal=" + hr.ToString()); } // add a stream to the sink writer. The mediaType specifies the format of the samples that will be written // to the file. Note that it does not necessarily need to match the input format. To set the input format // use SetInputMediaType. In this case it does match because we copied the video encoder information directly // out of the video source type hr = sinkWriter.AddStream(sourceReaderNativeVideoMediaType, out sinkWriterOutputVideoStreamId); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed adding the output stream(v), retVal=" + hr.ToString()); } // Set the input format for a stream on the sink writer. Note the use of the stream index here // The input format does not have to match the target format that is written to the media sink // If the formats do not match, this call attempts to load an transform // that can convert from the input format to the target format. If it cannot find one, and this is not // a sure thing, it will throw an exception. hr = sinkWriter.SetInputMediaType(sinkWriterOutputVideoStreamId, sourceReaderNativeVideoMediaType, null); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed on calling SetInputMediaType(v) on the writer, retVal=" + hr.ToString()); } } // if we have an audio stream in the source file we now set it up if (sourceReaderNativeAudioMediaType != null) { // set the media type on the reader - this is the media type it will output hr = sourceReader.SetCurrentMediaType(sourceReaderAudioStreamId, null, sourceReaderNativeAudioMediaType); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: failed on call to SetCurrentMediaType(a), retVal=" + hr.ToString()); } // add a stream to the sink writer. The mediaType specifies the format of the samples that will be written // to the file. Note that it does not necessarily need to match the input format. To set the input format // use SetInputMediaType. In this case it does match because we copied the video encoder information directly // out of the video source type hr = sinkWriter.AddStream(sourceReaderNativeAudioMediaType, out sinkWriterOutputAudioStreamId); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed adding the output stream(a), retVal=" + hr.ToString()); } // Set the input format for a stream on the sink writer. Note the use of the stream index here // The input format does not have to match the target format that is written to the media sink // If the formats do not match, this call attempts to load an transform // that can convert from the input format to the target format. If it cannot find one, and this is not // a sure thing, it will throw an exception. hr = sinkWriter.SetInputMediaType(sinkWriterOutputAudioStreamId, sourceReaderNativeAudioMediaType, null); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed on calling SetInputMediaType(a) on the writer, retVal=" + hr.ToString()); } } // begin writing on the sink writer hr = sinkWriter.BeginWriting(); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: failed on call to BeginWriting, retVal=" + hr.ToString()); } // we sit in a loop here and get the sample from the source reader and write it out // to the sink writer. An EOS (end of sample) value in the flags will signal the end. while (true) { int actualStreamIndex; MF_SOURCE_READER_FLAG actualStreamFlags; long timeStamp = 0; IMFSample workingMediaSample = null; // Request the next sample from the media source. Note that this could be // any type of media sample (video, audio, subtitles etc). We do not know // until we look at the stream ID. We saved the stream ID earlier when // we obtained the media types and so we can branch based on that. hr = sourceReader.ReadSample( TantaWMFUtils.MF_SOURCE_READER_ANY_STREAM, 0, out actualStreamIndex, out actualStreamFlags, out timeStamp, out workingMediaSample ); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed on calling the ReadSample on the reader, retVal=" + hr.ToString()); } // the sample may be null if either end of stream or a stream tick is returned if (workingMediaSample == null) { // just ignore, the flags will have the information we need. } else { // the sample is not null if (actualStreamIndex == sourceReaderAudioStreamId) { // audio data // ensure discontinuity is set for the first sample in each stream if (audioSamplesProcessed == 0) { // audio data hr = workingMediaSample.SetUINT32(MFAttributesClsid.MFSampleExtension_Discontinuity, 1); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed on calling SetUINT32 on the sample, retVal=" + hr.ToString()); } // remember this - we only do it once audioSamplesProcessed++; } hr = sinkWriter.WriteSample(sinkWriterOutputAudioStreamId, workingMediaSample); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed on calling the WriteSample on the writer, retVal=" + hr.ToString()); } } else if (actualStreamIndex == sourceReaderVideoStreamId) { // video data // ensure discontinuity is set for the first sample in each stream if (videoSamplesProcessed == 0) { // video data hr = workingMediaSample.SetUINT32(MFAttributesClsid.MFSampleExtension_Discontinuity, 1); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed on calling SetUINT32 on the sample, retVal=" + hr.ToString()); } // remember this - we only do it once videoSamplesProcessed++; } hr = sinkWriter.WriteSample(sinkWriterOutputVideoStreamId, workingMediaSample); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed on calling the WriteSample on the writer, retVal=" + hr.ToString()); } } // release the sample if (workingMediaSample != null) { Marshal.ReleaseComObject(workingMediaSample); workingMediaSample = null; } } // do we have a stream tick event? if ((actualStreamFlags & MF_SOURCE_READER_FLAG.StreamTick) != 0) { if (actualStreamIndex == sourceReaderVideoStreamId) { // video stream hr = sinkWriter.SendStreamTick(sinkWriterOutputVideoStreamId, timeStamp); } else if (actualStreamIndex == sourceReaderAudioStreamId) { // audio stream hr = sinkWriter.SendStreamTick(sinkWriterOutputAudioStreamId, timeStamp); } else { } } // is this stream at an END of Segment if ((actualStreamFlags & MF_SOURCE_READER_FLAG.EndOfStream) != 0) { // We have an EOS - but is it on the video or audio channel? // we have to get it on both if (actualStreamIndex == sourceReaderVideoStreamId) { // video stream // have we seen this before? if (videoStreamIsAtEOS == false) { hr = sinkWriter.NotifyEndOfSegment(sinkWriterOutputVideoStreamId); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed on calling the NotifyEndOfSegment on video stream, retVal=" + hr.ToString()); } videoStreamIsAtEOS = true; } } else if (actualStreamIndex == sourceReaderAudioStreamId) { // audio stream // have we seen this before? if (audioStreamIsAtEOS == false) { hr = sinkWriter.NotifyEndOfSegment(sinkWriterOutputAudioStreamId); if (hr != HResult.S_OK) { // we failed throw new Exception("CopyFile: Failed on calling the NotifyEndOfSegment on audio stream, retVal=" + hr.ToString()); } audioStreamIsAtEOS = true; } // audio stream } else { } // our exit condition depends on which streams are in use if ((sourceReaderNativeVideoMediaType != null) && (sourceReaderNativeAudioMediaType != null)) { // if both streams are at EOS we can leave if ((videoStreamIsAtEOS == true) && (audioStreamIsAtEOS == true)) { break; } } else if (sourceReaderNativeVideoMediaType != null) { // only video is active, if the video stream is EOS we can leave if (videoStreamIsAtEOS == true) { break; } } else if (sourceReaderNativeAudioMediaType != null) { // only audio is active, if the audio stream is EOS we can leave if (audioStreamIsAtEOS == true) { break; } } } } // bottom of endless for loop hr = sinkWriter.Finalize_(); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed on call tosinkWriter.Finalize(), retVal=" + hr.ToString()); } }