public void Convert(Texture2D Texture, byte[] Output) { Texture.Device.ImmediateContext.CopyResource(Texture, _copyTexture); _inputSample.SampleTime = _frameNumber++ *1_000_000; _colorConverter.ProcessInput(0, _inputSample, 0); var buf = new MFTOutputDataBuffer[1]; // HACK: Need to use Media Foundation .NET here due to bug in SharpDX.MediaFoundation (no longer maintained). // I think the output data buffer is not [Out] marshalled. ((IMFTransform)Marshal.GetObjectForIUnknown(_colorConverter.NativePointer)).ProcessOutput(0, 1, buf, out _); using (var sample = new Sample(buf[0].pSample)) { using (var buffer = sample.GetBufferByIndex(0)) { var ptr = buffer.Lock(out _, out _); Marshal.Copy(ptr, Output, 0, Output.Length); buffer.Unlock(); } } }
protected override HResult OnProcessOutput(ref MFTOutputDataBuffer pOutputSamples) { HResult hr = HResult.S_OK; if (pOutputSamples.pSample == IntPtr.Zero) { // Synchronous MFTs don't (by default) have an IMFAttributes. // So I'm putting the sample number on the actual sample, just // to have some place to put it. MFError throwonhr = InputSample.SetUINT32(m_SampleCountGuid, m_iSampleCount); m_iSampleCount++; // The output sample is the input sample. pOutputSamples.pSample = Marshal.GetIUnknownForObject(InputSample); // Release the current input sample so we can get another one. InputSample = null; } else { hr = HResult.E_INVALIDARG; } return(hr); }
override protected HResult OnProcessOutput(ref MFTOutputDataBuffer pOutputSamples) { HResult hr; // Since we specified MFTOutputStreamInfoFlags.ProvidesSamples, this should be null. if (pOutputSamples.pSample == IntPtr.Zero) { // Set status flags. pOutputSamples.dwStatus = MFTOutputDataBufferFlags.None; // Wait for the thread to finish processing. m_SampleDone.WaitOne(); pOutputSamples.pSample = Marshal.GetIUnknownForObject(InputSample); // This does an implicit Marshal.ReleaseComObject, which is // a bit risky, since some other .Net component could have a // pointer to this same object, and this would yank the RCW // out from under them. But we go thru a LOT of samples. InputSample = null; hr = m_hr; } else { hr = HResult.E_INVALIDARG; } return(hr); }
/// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+= /// <summary> /// This is the routine that performs the transform. Assumes InputSample is set. /// /// An override of the abstract version in TantaMFTBase_Sync. /// </summary> /// <param name="outputSampleDataStruct">The structure to populate with output data.</param> /// <returns>S_Ok unless error.</returns> /// <history> /// 01 Nov 18 Cynic - Ported In /// </history> protected override HResult OnProcessOutput(ref MFTOutputDataBuffer outputSampleDataStruct) { HResult hr = HResult.S_OK; IMFMediaBuffer outputMediaBuffer = null; // we are processing in place, the input sample is the output sample, the media buffer of the // input sample is the media buffer of the output sample. try { // Get the data buffer from the input sample. If the sample contains more than one buffer, // this method copies the data from the original buffers into a new buffer, and replaces // the original buffer list with the new buffer. The new buffer is returned in the inputMediaBuffer parameter. // If the sample contains a single buffer, this method returns a pointer to the original buffer. // In typical use, most samples do not contain multiple buffers. hr = InputSample.ConvertToContiguousBuffer(out outputMediaBuffer); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to InputSample.ConvertToContiguousBuffer failed. Err=" + hr.ToString()); } // now that we have an output buffer, do the work to implement the appropriate rotate mode. // Writing into outputMediaBuffer will write to the approprate location in the outputSample FlipImageInBuffer(outputMediaBuffer); // increment this for the client/transform communications demonstrator code m_FrameCount++; // Set status flags. outputSampleDataStruct.dwStatus = MFTOutputDataBufferFlags.None; // The output sample is the input sample. We get a new IUnknown for the Input // sample since we are going to release it below. The client will release this // new IUnknown outputSampleDataStruct.pSample = Marshal.GetIUnknownForObject(InputSample); } finally { // clean up SafeRelease(outputMediaBuffer); // Release the current input sample so we can get another one. // the act of setting it to null releases it because the property // is coded that way InputSample = null; } return(HResult.S_OK); }
/// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+= /// <summary> /// This is the routine that performs the transform. All we do here is count /// the sample and pass it on. /// /// An override of the abstract version in TantaMFTBase_Sync. /// </summary> /// <param name="pOutputSamples">The structure to populate with output values.</param> /// <returns>S_Ok unless error.</returns> /// <history> /// 01 Nov 18 Cynic - Ported In /// </history> protected override HResult OnProcessOutput(ref MFTOutputDataBuffer pOutputSamples) { HResult hr = HResult.S_OK; if (pOutputSamples.pSample == IntPtr.Zero) { // this is the line that does the work. Simple isn't it? m_FrameCount++; // Actually there is more going on here than is obvious. We have an // Input sample supplied earlier, that sample will contain a MediaBuffer which // in turn will contain the data. If we were doing anything with that data we could // modify it now and pass it back. However, as you will note when you look at the // other sample transforms, the data is in unmanaged memory space and so // will need to be brought up into an object C# can work on. This can be done // via a variety of methods but "unsafe" copies are generally used for speed // reasons. // We are doing in-place processing, the output sample is the input sample. pOutputSamples.pSample = Marshal.GetIUnknownForObject(InputSample); // the output data does not have to be (optinally modified) and handed right // back as the output data. We could create a new output sample and copy the // data across to it. The TantaMFTGrayscale does this. However the // OnGetInputStreamInfo and OnGetOutputStreamInfo overrides would have to // be modified so the client knows that we are creating our own samples. // Release the current input sample so we can get another one. // the act of setting it to null releases it because the property // is coded that way InputSample = null; } else { hr = HResult.E_INVALIDARG; } return(hr); }
protected HRESULT ProcessOutput() { try { //ASSERT(m_bSampleNotify || m_bRepaint); // See note above. HRESULT hr = S_OK; ProcessOutputStatus dwStatus = 0; long mixerStartTime = 0, mixerEndTime = 0; long systemTime = 0; bool bRepaint = m_bRepaint; // Temporarily store this state flag. MFTOutputDataBuffer dataBuffer = new MFTOutputDataBuffer(); MFSample pSample = null; // If the clock is not running, we present the first sample, // and then don't present any more until the clock starts. if ((m_RenderState != RENDER_STATE.RENDER_STATE_STARTED) && // Not running. !m_bRepaint && // Not a repaint request. m_bPrerolled // At least one sample has been presented. ) { return S_FALSE; } // Make sure we have a pointer to the mixer. if (m_pMixer == IntPtr.Zero) { return MFHelper.MF_E_INVALIDREQUEST; } // Try to get a free sample from the video sample pool. hr = (m_SamplePool.GetBuffer(out pSample, false) ? S_OK : MFHelper.MF_E_SAMPLEALLOCATOR_EMPTY); if (hr == MFHelper.MF_E_SAMPLEALLOCATOR_EMPTY) { return S_FALSE; // No free samples. We'll try again when a sample is released. } // Fail on any other error code. if (hr.Succeeded) { // From now on, we have a valid video sample pointer, where the mixer will // write the video data. ASSERT(pSample != null); // (If the following assertion fires, it means we are not managing the sample pool correctly.) if (m_bRepaint) { // Repaint request. Ask the mixer for the most recent sample. MFHelper.SetDesiredSampleTime(pSample.Sample, m_scheduler.LastSampleTime, m_scheduler.FrameDuration); m_bRepaint = FALSE; // OK to clear this flag now. } else { // Not a repaint request. Clear the desired sample time; the mixer will // give us the next frame in the stream. ClearDesiredSampleTime(pSample); if (m_pClock != null) { // Latency: Record the starting time for the ProcessOutput operation. m_pClock.GetCorrelatedTime(0, out mixerStartTime, out systemTime); } } // Now we are ready to get an output sample from the mixer. dataBuffer.dwStreamID = 0; dataBuffer.pSample = Marshal.GetIUnknownForObject(pSample.Sample); dataBuffer.dwStatus = 0; { IMFTransform pMixer = (IMFTransform)Marshal.GetObjectForIUnknown(m_pMixer); hr = (HRESULT)pMixer.ProcessOutput(0, 1, new MFTOutputDataBuffer[] { dataBuffer }, out dwStatus); } if (hr.Failed) { // Return the sample to the pool. m_SamplePool.ReleaseBuffer(pSample); // Handle some known error codes from ProcessOutput. if (hr == MFHelper.MF_E_TRANSFORM_TYPE_NOT_SET) { // The mixer's format is not set. Negotiate a new format. hr = RenegotiateMediaType(); } else if (hr == MFHelper.MF_E_TRANSFORM_STREAM_CHANGE) { // There was a dynamic media type change. Clear our media type. SetMediaType(null); } else if (hr == MFHelper.MF_E_TRANSFORM_NEED_MORE_INPUT) { // The mixer needs more input. // We have to wait for the mixer to get more input. m_bSampleNotify = false; } } else { // We got an output sample from the mixer. if (m_pClock != null && !bRepaint) { // Latency: Record the ending time for the ProcessOutput operation, // and notify the EVR of the latency. m_pClock.GetCorrelatedTime(0, out mixerEndTime, out systemTime); long latencyTime = mixerEndTime - mixerStartTime; IntPtr _ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(latencyTime)); try { Marshal.WriteInt64(_ptr, latencyTime); NotifyEvent(DsEvCode.ProcessingLatency, _ptr, IntPtr.Zero); } finally { Marshal.FreeCoTaskMem(_ptr); } } if (hr.Succeeded) { // Schedule the sample. hr = DeliverSample(pSample, bRepaint); } if (hr.Succeeded) { m_bPrerolled = true; // We have presented at least one sample now. } } } // Release any events that were returned from the ProcessOutput method. // (We don't expect any events from the mixer, but this is a good practice.) if (dataBuffer.pEvents != null) { Marshal.ReleaseComObject(dataBuffer.pEvents); } pSample = null; return hr; } catch (Exception _exception) { throw _exception; } }
/// <summary> /// This is the routine that performs the transform. Unless the sinkWriter object /// is set all we do is pass the sample on. If the sink writer object is set /// we give the sample to it for writing. There are two modes - one where we just /// give the sinkwriter the input sample and the other where we clone the input /// sample and rebase the timestamps. /// /// An override of the abstract version in MFTBase_Sync. /// </summary> /// <param name="pOutputSamples">The structure to populate with output values.</param> /// <returns>S_Ok unless error.</returns> protected override HResult OnProcessOutput(ref MFTOutputDataBuffer outputSampleDataStruct) { HResult hr = HResult.S_OK; IMFMediaBuffer inputMediaBuffer = null; IMFSample sinkWriterSample = null; IMFAttributes sampleAttributes; // in this MFT we are processing in place, the input sample is the output sample, the media buffer of the // input sample is the media buffer of the output sample. Thats for the pipeline. If a sink writer exists // we also write the sample data out to the sink writer. This provides the effect of displaying on the // screen and simultaneously recording. // There are two ways the sink writer can be given the media sample data. It can just be given the // input sample directly or a copy of the sample can be made and that copy given to the sink writer. // There is also an additional complication - the sample has a timestamp and video cameras tend // to just use the current date and time as a timestamp. There are several reports that MP4 files // need to have their first frame starting at zero and then every subsequent frame adjusted to that // new base time. Certainly the Microsoft supplied example code (and see the // CaptureToFileViaReaderWriter also) take great care to do this. This requirement does not // seem to exist - my tests indicate it is not necessary to start from 0 in the mp4 file. Maybe the // Sink Writer has been improved and now does this automatically. For demonstration purposes // the timebase-rebase functionality has been included and choosing that mode copies the sample // and resets the time. If the user does not rebase the time we simply send the input sample // to the Sink Writer as-is. try { // Set status flags. outputSampleDataStruct.dwStatus = MFTOutputDataBufferFlags.None; // The output sample is the input sample. We get a new IUnknown for the Input // sample since we are going to release it below. The client will release this // new IUnknown outputSampleDataStruct.pSample = Marshal.GetIUnknownForObject(InputSample); // are we recording? if (workingSinkWriter != null) { // we do everything in a lock lock (sinkWriterLockObject) { // are we in timebase rebase mode? if (wantTimebaseRebase == false) { // we are not. Just give the input sample to the Sink Writer which will // write it out. hr = workingSinkWriter.WriteSample(sinkWriterVideoStreamId, InputSample); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to WriteSample(a) failed. Err=" + hr.ToString()); } } else { // the timebase rebase option has been chosen. We need to create a copy of the input sample // so we can adjust the time on it. // Get the data buffer from the input sample. If the sample contains more than one buffer, // this method copies the data from the original buffers into a new buffer, and replaces // the original buffer list with the new buffer. The new buffer is returned in the inputMediaBuffer parameter. // If the sample contains a single buffer, this method returns a pointer to the original buffer. // In typical use, most samples do not contain multiple buffers. hr = InputSample.ConvertToContiguousBuffer(out inputMediaBuffer); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to InputSample.ConvertToContiguousBuffer failed. Err=" + hr.ToString()); } // get some other things from the input sample hr = InputSample.GetSampleDuration(out long sampleDuration); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to InputSample.GetSampleDuration failed. Err=" + hr.ToString()); } hr = InputSample.GetTotalLength(out int sampleSize); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to InputSample.GetTotalLength failed. Err=" + hr.ToString()); } hr = InputSample.GetSampleTime(out long sampleTimeStamp); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to InputSample.GetSampleTime failed. Err=" + hr.ToString()); } // get the attributes from the input sample if (InputSample is IMFAttributes) { sampleAttributes = (InputSample as IMFAttributes); } else { sampleAttributes = null; } // we have all the information we need to create a new output sample sinkWriterSample = WMFUtils.CreateMediaSampleFromBuffer(sampleTimeStamp, sampleDuration, inputMediaBuffer, sampleSize, sampleAttributes); if (sinkWriterSample == null) { throw new Exception("OnProcessOutput, Error on call to CreateMediaSampleFromBuffer sinkWriterSample == null"); } // we have a sample, if so is it the first non null one? if (isFirstSample) { // yes it is set up our timestamp firstSampleBaseTime = sampleTimeStamp; isFirstSample = false; } // rebase the time stamp sampleTimeStamp -= firstSampleBaseTime; hr = sinkWriterSample.SetSampleTime(sampleTimeStamp); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to InputSample.SetSampleTime failed. Err=" + hr.ToString()); } // write the sample out hr = workingSinkWriter.WriteSample(sinkWriterVideoStreamId, sinkWriterSample); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to WriteSample(b) failed. Err=" + hr.ToString()); } } } } } finally { // clean up if (inputMediaBuffer != null) { Marshal.ReleaseComObject(inputMediaBuffer); } if (sinkWriterSample != null) { Marshal.ReleaseComObject(sinkWriterSample); } // Release the current input sample so we can get another one. // the act of setting it to null releases it because the property // is coded that way InputSample = null; } return(HResult.S_OK); }
private void CaptureStillImages(MediaItem item) { using (var releaser = new ComReleaser()) { MF.CreateVideoDeviceSource(item.DeviceItem.SymLink, out IMFMediaSource source); releaser.Add(source); source.CreatePresentationDescriptor(out IMFPresentationDescriptor presDesc); releaser.Add(presDesc); presDesc.GetStreamDescriptorByIndex(item.DescIndex, out bool selected, out IMFStreamDescriptor strmDesc); releaser.Add(strmDesc); strmDesc.GetMediaTypeHandler(out IMFMediaTypeHandler handler); releaser.Add(handler); handler.GetMediaTypeByIndex(item.TypeIndex, out IMFMediaType type); handler.SetCurrentMediaType(type); MF.CreateSourceReaderFromMediaSource(source, out IMFSourceReader reader); if (reader == null) { return; } releaser.Add(reader); IMFTransform transform = null; MFTOutputDataBuffer[] outSamples = null; IMFSample outRgb24Sample = null; IMFMediaBuffer outRgb24Buffer = null; int rgbSize = item.Width * item.Height * 3; var needToConvert = item.SubType != MFMediaType.RGB24; if (needToConvert) { var processor = new VideoProcessorMFT(); releaser.Add(processor); transform = (IMFTransform)processor; HR(transform.SetInputType(0, type, MFTSetTypeFlags.None)); var rgbMediaType = MF.CreateMediaType(); releaser.Add(rgbMediaType); HR(type.CopyAllItems(rgbMediaType)); HR(rgbMediaType.SetGUID(MFAttributesClsid.MF_MT_SUBTYPE, MFMediaType.RGB24)); HR(rgbMediaType.SetUINT32(MFAttributesClsid.MF_MT_DEFAULT_STRIDE, 3 * item.Width)); HR(rgbMediaType.SetUINT32(MFAttributesClsid.MF_MT_SAMPLE_SIZE, rgbSize)); HR(transform.SetOutputType(0, rgbMediaType, MFTSetTypeFlags.None)); outSamples = new MFTOutputDataBuffer[1]; outSamples[0] = new MFTOutputDataBuffer(); outRgb24Sample = MF.CreateSample(); releaser.Add(outRgb24Sample); outRgb24Buffer = MF.CreateMemoryBuffer(rgbSize); releaser.Add(outRgb24Buffer); outRgb24Sample.AddBuffer(outRgb24Buffer); outSamples[0].pSample = Marshal.GetIUnknownForObject(outRgb24Sample); } while (true) { int frames = 0; var hrRS = reader.ReadSample( (int)MF_SOURCE_READER.AnyStream, MF_SOURCE_READER_CONTROL_FLAG.None, out int streamIndex, out MF_SOURCE_READER_FLAG flags, out long timeStamp, out IMFSample sample ); if (sample != null) { try { IMFSample rgbSample = sample; if (transform != null) { transform.ProcessInput(0, sample, 0); while (true) { var hrPO = transform.ProcessOutput( MFTProcessOutputFlags.None, 1, outSamples, out ProcessOutputStatus status ); if (hrPO.Succeeded()) { ConsumeBuffer(outRgb24Buffer, item); frames++; Marshal.ReleaseComObject(sample); return; //break; } else { break; } } //var hrPI = transform.ProcessInput(0, sample, 0); continue; } rgbSample.GetBufferByIndex(0, out IMFMediaBuffer buff); if (ConsumeBuffer(buff, item)) { frames++; } else { return; } } finally { Marshal.ReleaseComObject(sample); } break; } } } }
override protected HResult OnProcessOutput(ref MFTOutputDataBuffer pOutputSamples) { HResult hr = HResult.S_OK; MFError throwonhr; // Since we don't specify MFTOutputStreamInfoFlags.ProvidesSamples, this can't be null. if (pOutputSamples.pSample != IntPtr.Zero) { long hnsDuration; long hnsTime; IMFMediaBuffer pInput = null; IMFMediaBuffer pOutput = null; IMFSample pOutSample = null; try { // Get the data buffer from the input sample. If the sample has // multiple buffers, you might be able to get (slightly) better // performance processing each buffer in turn rather than forcing // a new, full-sized buffer to get created. throwonhr = InputSample.ConvertToContiguousBuffer(out pInput); // Turn pointer to interface pOutSample = Marshal.GetUniqueObjectForIUnknown(pOutputSamples.pSample) as IMFSample; // Get the output buffer. throwonhr = pOutSample.ConvertToContiguousBuffer(out pOutput); OnProcessOutput(pInput, pOutput); // Set status flags. pOutputSamples.dwStatus = MFTOutputDataBufferFlags.None; // Copy the duration and time stamp from the input sample, // if present. hr = InputSample.GetSampleDuration(out hnsDuration); if (Succeeded(hr)) { throwonhr = pOutSample.SetSampleDuration(hnsDuration); } hr = InputSample.GetSampleTime(out hnsTime); if (Succeeded(hr)) { throwonhr = pOutSample.SetSampleTime(hnsTime); } } finally { SafeRelease(pInput); SafeRelease(pOutput); SafeRelease(pOutSample); // Release the current input sample so we can get another one. InputSample = null; } } else { return(HResult.E_INVALIDARG); } return(HResult.S_OK); }
/// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+= /// <summary> /// This is the routine that performs the transform. Assumes InputSample is set. /// /// An override of the abstract version in TantaMFTBase_Sync. /// </summary> /// <param name="outputSampleDataStruct">The structure to populate with output data.</param> /// <returns>S_Ok unless error.</returns> /// <history> /// 01 Nov 18 Cynic - Ported In /// </history> protected override HResult OnProcessOutput(ref MFTOutputDataBuffer outputSampleDataStruct) { long hnsDuration; long hnsTime; HResult hr = HResult.S_OK; IMFMediaBuffer inputMediaBuffer = null; IMFMediaBuffer outputMediaBuffer = null; IMFSample outputSample = null; // Since we don't specify MFTOutputStreamInfoFlags.ProvidesSamples, this can't be null. // we expect the caller to have allocated the memory for this if (outputSampleDataStruct.pSample == IntPtr.Zero) { return(HResult.E_INVALIDARG); } try { // Get the data buffer from the input sample. If the sample contains more than one buffer, // this method copies the data from the original buffers into a new buffer, and replaces // the original buffer list with the new buffer. The new buffer is returned in the inputMediaBuffer parameter. // If the sample contains a single buffer, this method returns a pointer to the original buffer. // In typical use, most samples do not contain multiple buffers. hr = InputSample.ConvertToContiguousBuffer(out inputMediaBuffer); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to InputSample.ConvertToContiguousBuffer failed. Err=" + hr.ToString()); } // Turn pointer into an interface. The GetUniqueObjectForIUnknown method ensures that we // receive a unique Runtime Callable Wrapper, because it does not match an IUnknown pointer // to an existing object. Use this method when you have to create a unique RCW that is not // impacted by other code that calls the ReleaseComObject method. outputSample = Marshal.GetUniqueObjectForIUnknown(outputSampleDataStruct.pSample) as IMFSample; if (outputSample == null) { throw new Exception("OnProcessOutput call to GetUniqueObjectForIUnknown failed. outputSample == null"); } // Now get the output buffer. A media sample contains zero or more buffers. Each buffer manages a block of // memory, and is represented by the IMFMediaBuffer interface. A sample can have multiple buffers. // The buffers are kept in an ordered list and accessed by index value. This call gets us a single // pointer to a single contigous buffer which is much more useful. hr = outputSample.ConvertToContiguousBuffer(out outputMediaBuffer); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to InputSample.ConvertToContiguousBuffer failed. Err=" + hr.ToString()); } // now that we have an input and output buffer, do the work to convert them to grayscale. // Writing into outputMediaBuffer will write to the approprate location in the outputSample // since we took care to Marshal it that way ConvertMediaBufferToGrayscale(inputMediaBuffer, outputMediaBuffer); // Set status flags. outputSampleDataStruct.dwStatus = MFTOutputDataBufferFlags.None; // Copy the duration from the input sample, if present. The // Media Session needs these in order to keep everything sync'ed hr = InputSample.GetSampleDuration(out hnsDuration); if (hr == HResult.S_OK) { hr = outputSample.SetSampleDuration(hnsDuration); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to OutSample.SetSampleDuration failed. Err=" + hr.ToString()); } } // Copy the time stamp from the input sample, if present. hr = InputSample.GetSampleTime(out hnsTime); if (hr == HResult.S_OK) { hr = outputSample.SetSampleTime(hnsTime); if (hr != HResult.S_OK) { throw new Exception("OnProcessOutput call to OutSample.SetSampleTime failed. Err=" + hr.ToString()); } } } finally { // clean up SafeRelease(inputMediaBuffer); SafeRelease(outputMediaBuffer); SafeRelease(outputSample); // Release the current input sample so we can get another one. // the act of setting it to null releases it because the property // is coded that way InputSample = null; } return(HResult.S_OK); }