示例#1
0
        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();
                }
            }
        }
示例#2
0
        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);
        }
示例#3
0
        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);
        }
示例#4
0
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <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);
        }
示例#5
0
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <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);
        }
示例#6
0
        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;
            }
        }
示例#7
0
        /// <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);
        }
示例#8
0
        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;
                    }
                }
            }
        }
示例#9
0
        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);
        }
示例#10
0
        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <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);
        }