private void PerformEncode(IMFSinkWriter writer, int streamIndex, MediaFoundationReader inputProvider, int first, int last) { int maxLength = inputProvider.WaveFormat.AverageBytesPerSecond * 4; var managedBuffer = new byte[maxLength]; writer.BeginWriting(); first++; bool flag = true; double time = Math.Round(inputProvider.TotalTime.TotalSeconds - last, 0); long position = 0; long duration; do { duration = ConvertOneBuffer(writer, streamIndex, inputProvider, position, managedBuffer, first, ref flag); position += duration; int percent = (int)(inputProvider.CurrentTime.TotalSeconds / inputProvider.TotalTime.TotalSeconds * 100); //LoadProgressChanged.Raise(this, new LoadProgressChangedEventArgs(percent, inputProvider.CurrentTime, inputProvider.TotalTime)); OnLoadProgressChanged(percent, inputProvider.CurrentTime, inputProvider.TotalTime); if (inputProvider.CurrentTime.TotalSeconds >= time) { duration = 0; } } while (duration > 0); writer.DoFinalize(); }
HResult ConfigureCapture(EncodingParameters eparam) { HResult hr = HResult.S_OK; int sink_stream = 0; IMFMediaType pType = null; hr = ConfigureSourceReader(m_pReader); if (Succeeded(hr)) { hr = m_pReader.GetCurrentMediaType( MF_SOURCE_READER_FIRST_VIDEO_STREAM, out pType ); } if (Succeeded(hr)) { hr = ConfigureEncoder(eparam, pType, m_pWriter, out sink_stream); } if (Succeeded(hr)) { // Register the color converter DSP for this process, in the video // processor category. This will enable the sink writer to enumerate // the color converter when the sink writer attempts to match the // media types. hr = MFExtern.MFTRegisterLocalByCLSID( typeof(CColorConvertDMO).GUID, MFTransformCategory.MFT_CATEGORY_VIDEO_PROCESSOR, "", MFT_EnumFlag.SyncMFT, 0, null, 0, null ); } if (Succeeded(hr)) { hr = m_pWriter.SetInputMediaType(sink_stream, pType, null); } if (Succeeded(hr)) { hr = m_pWriter.BeginWriting(); } SafeRelease(pType); return(hr); }
private int ConfigureCapture(EncodingParameters eparam) { IMFMediaType pType = null; //var hr = ConfigureSourceReader(PReader); var hr = PReader.GetCurrentMediaType((int)MF_SOURCE_READER.FirstVideoStream, out pType); int w, h; MfGetAttributeSize(pType, out w, out h); eparam.Bitrate = w * h * 20; var sinkStream = 0; if (Succeeded(hr)) { hr = ConfigureEncoder(eparam, pType, PWriter, out sinkStream); } if (Succeeded(hr)) { // Register the color converter DSP for this process, in the video // processor category. This will enable the sink writer to enumerate // the color converter when the sink writer attempts to match the // media types. hr = MFExtern.MFTRegisterLocalByCLSID( typeof(CColorConvertDMO).GUID, MFTransformCategory.MFT_CATEGORY_VIDEO_PROCESSOR, "", MFT_EnumFlag.SyncMFT, 0, null, 0, null ); } if (Succeeded(hr)) { hr = PWriter.SetInputMediaType(sinkStream, pType, null); } if (Succeeded(hr)) { hr = PWriter.BeginWriting(); } SafeRelease(pType); return(hr); }
HResult ConfigWriter() { const int MF_SOURCE_READER_FIRST_VIDEO_STREAM = unchecked ((int)0xfffffffc); IMFMediaType pType = null; int sink_stream = 0; m_bFirstSample = true; m_llBaseTime = 0; HResult hr = MFExtern.MFCreateSinkWriterFromURL( OutputFileName, null, null, out m_pWriter ); if (Succeeded(hr)) { hr = m_pReader.GetCurrentMediaType( MF_SOURCE_READER_FIRST_VIDEO_STREAM, out pType ); } if (Succeeded(hr)) { hr = ConfigureEncoder(pType, m_pWriter, out sink_stream); } if (Succeeded(hr)) { hr = MFExtern.MFTRegisterLocalByCLSID( typeof(CColorConvertDMO).GUID, MFTransformCategory.MFT_CATEGORY_VIDEO_PROCESSOR, "", MFT_EnumFlag.SyncMFT, 0, null, 0, null ); } if (Succeeded(hr)) { hr = m_pWriter.SetInputMediaType(sink_stream, pType, null); } if (Succeeded(hr)) { hr = m_pWriter.BeginWriting(); } return(hr); }
private void PerformEncode(IMFSinkWriter writer, int streamIndex, IWaveProvider inputProvider) { int num = inputProvider.WaveFormat.AverageBytesPerSecond * 4; byte[] managedBuffer = new byte[num]; writer.BeginWriting(); long num2 = 0L; long num3; do { num3 = this.ConvertOneBuffer(writer, streamIndex, inputProvider, num2, managedBuffer); num2 += num3; } while (num3 > 0L); writer.DoFinalize(); }
private void PerformEncode(IMFSinkWriter writer, int streamIndex, IWaveProvider inputProvider) { int maxLength = inputProvider.WaveFormat.AverageBytesPerSecond * 4; var managedBuffer = new byte[maxLength]; writer.BeginWriting(); long position = 0; long duration = 0; do { duration = ConvertOneBuffer(writer, streamIndex, inputProvider, position, managedBuffer); position += duration; } while (duration > 0); writer.DoFinalize(); }
private void PerformEncode(IMFSinkWriter writer, int streamIndex, IWaveProvider inputProvider) { int maxLength = inputProvider.WaveFormat.AverageBytesPerSecond*4; var managedBuffer = new byte[maxLength]; writer.BeginWriting(); long position = 0; long duration = 0; do { duration = ConvertOneBuffer(writer, streamIndex, inputProvider, position, managedBuffer); position += duration; } while (duration > 0); //We are going to check the stats of the writer and ensure no more bytes are queued before we call DoFinalize() MF_SINK_WRITER_STATISTICS stats = new MF_SINK_WRITER_STATISTICS(); stats.cb = Marshal.SizeOf(stats); bool finished = false; const int sleepMs = 10; const int maxTimeInSeconds = 5; const int maxTries = (maxTimeInSeconds*1000)/sleepMs; var tries = 0; try { while (!finished) { writer.GetStatistics(streamIndex, stats); if (tries == maxTries) { var msg = string.Format( "Unable to save the MP3 file. Sink writer still has {0} bytes in the queue after waiting for {1} seconds.", stats.dwByteCountQueued, maxTimeInSeconds); throw new Exception(msg); } if (stats.dwByteCountQueued == 0) { finished = true; } else { //sleeping could be optional, but don't want to hog the CPU System.Threading.Thread.Sleep(sleepMs); } tries++; } } finally { writer.DoFinalize(); } }
/// <summary> /// Starts the process of recording. creates the sink writer. We do not /// check to see if the filename is viable or already exists. This is /// assumed to have been done before this call. /// </summary> /// <param name="outputFileName">the output file name</param> /// <param name="incomingVideoMediaType">the incoming media type</param> /// <param name="wantTimebaseRebaseIn">if true we rebase all incoming sample /// times to zero from the point we started recording and send a copy of the /// sample to the sink writer instead of the input sample</param> /// <returns>z success, nz fail</returns> public int StartRecording(string outputFileName, IMFMediaType incomingVideoMediaType, bool wantTimebaseRebaseIn) { HResult hr; LogMessage("MFTSampleGrabber_Sync, StartRecording called"); // first stop any recordings now StopRecording(); // check the output file name for sanity if ((outputFileName == null) || (outputFileName.Length == 0)) { LogMessage("StartRecording (outputFileName==null)|| (outputFileName.Length==0)"); return(100); } // check the media type for sanity if (incomingVideoMediaType == null) { LogMessage("StartRecording videoMediaType == null"); return(150); } lock (sinkWriterLockObject) { // create the sink writer workingSinkWriter = OpenSinkWriter(outputFileName, true); if (workingSinkWriter == null) { LogMessage("StartRecording failed to create sink writer"); return(200); } // now configure the SinkWriter. This sets up the sink writer so that it knows what format // the output data should be written in. The format we give the writer does not // need to be the same as the format receives as input - however to make life easier for ourselves // we will copy a lot of the settings from the videoType retrieved above // create a new empty media type for us to populate hr = MFExtern.MFCreateMediaType(out IMFMediaType encoderType); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed on call to MFCreateMediaType, retVal=" + hr.ToString()); } // The major type defines the overall category of the media data. Major types include video, audio, script & etc. hr = encoderType.SetGUID(MFAttributesClsid.MF_MT_MAJOR_TYPE, MFMediaType.Video); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed setting the MF_MT_MAJOR_TYPE, retVal=" + hr.ToString()); } // The subtype GUID defines a specific media format type within a major type. For example, within video, // the subtypes include MFMediaType.H264 (MP4), MFMediaType.WMV3 (WMV), MJPEG & etc. Within audio, the // subtypes include PCM audio, Windows Media Audio 9, & etc. hr = encoderType.SetGUID(MFAttributesClsid.MF_MT_SUBTYPE, MEDIA_TYPETO_WRITE); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed setting the MF_MT_SUBTYPE, retVal=" + hr.ToString()); } // this is the approximate data rate of the video stream, in bits per second, for a // video media type. The choice here is somewhat arbitrary but seems to work well. hr = encoderType.SetUINT32(MFAttributesClsid.MF_MT_AVG_BITRATE, TARGET_BIT_RATE); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed setting the MF_MT_AVG_BITRATE, retVal=" + hr.ToString()); } // populate our new encoding type with the frame size of the videoType selected earlier hr = WMFUtils.CopyAttributeData(incomingVideoMediaType, encoderType, MFAttributesClsid.MF_MT_FRAME_SIZE); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed copying the MF_MT_FRAME_SIZE, retVal=" + hr.ToString()); } // populate our new encoding type with the frame rate of the video type selected earlier hr = WMFUtils.CopyAttributeData(incomingVideoMediaType, encoderType, MFAttributesClsid.MF_MT_FRAME_RATE); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed copying the MF_MT_FRAME_RATE, retVal=" + hr.ToString()); } // populate our new encoding type with the pixel aspect ratio of the video type selected earlier hr = WMFUtils.CopyAttributeData(incomingVideoMediaType, encoderType, MFAttributesClsid.MF_MT_PIXEL_ASPECT_RATIO); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed copying the MF_MT_PIXEL_ASPECT_RATIO, retVal=" + hr.ToString()); } // populate our new encoding type with the interlace mode of the video type selected earlier hr = WMFUtils.CopyAttributeData(incomingVideoMediaType, encoderType, MFAttributesClsid.MF_MT_INTERLACE_MODE); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed copying the MF_MT_INTERLACE_MODE, retVal=" + hr.ToString()); } // add a stream to the sink writer for the output Media type. The // incomingVideoMediaType 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. hr = workingSinkWriter.AddStream(encoderType, out sinkWriterVideoStreamId); if (hr != HResult.S_OK) { // we failed throw new Exception("StartRecording Failed adding the output stream(v), retVal=" + hr.ToString()); } // Windows 10, by default, provides an adequate set of codecs which the Sink Writer can // find to write out the MP4 file. This is not true on Windows 7. // If we are not on Windows 10 we register (locally) a codec // the Sink Writer can find and use. The ColorConvertDMO is supplied by // microsoft it is just not available to enumerate on Win7 etc. // Making it available locally does not require administrator privs // but only this process can see it and it disappears when the process // closes OperatingSystem os = Environment.OSVersion; int versionID = ((os.Version.Major * 10) + os.Version.Minor); if (versionID < 62) { Guid ColorConverterDMOGUID = new Guid("98230571-0087-4204-b020-3282538e57d3"); // Register the color converter DSP for this process, in the video // processor category. This will enable the sink writer to enumerate // the color converter when the sink writer attempts to match the // media types. hr = MFExtern.MFTRegisterLocalByCLSID( ColorConverterDMOGUID, MFTransformCategory.MFT_CATEGORY_VIDEO_PROCESSOR, "", MFT_EnumFlag.SyncMFT, 0, null, 0, null ); } // 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 = workingSinkWriter.SetInputMediaType(sinkWriterVideoStreamId, incomingVideoMediaType, null); if (hr != HResult.S_OK) { // we failed throw new Exception("StartRecording Failed on calling SetInputMediaType(v) on the writer, retVal=" + hr.ToString()); } // set this flag now wantTimebaseRebase = wantTimebaseRebaseIn; // now we initialize the sink writer for writing. We call this method after configuring the // input streams but before we send any data to the sink writer. The underlying media sink must // have at least one input stream and we know it does because we set it up above hr = workingSinkWriter.BeginWriting(); if (hr != HResult.S_OK) { // we failed throw new Exception("StartRecording Failed on calling BeginWriting on the writer, retVal=" + hr.ToString()); } } return(0); }
/// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+= /// <summary> /// Starts capture of the data to a file. /// /// Because this code is intended for demo purposes and in the interests of /// reducing complexity it is extremely linear and step-by-step. Doubtless /// there is much refactoring that could be done. /// /// </summary> /// <history> /// 01 Nov 18 Cynic - Started /// </history> private void CaptureToFile() { HResult hr; IMFMediaType videoType = null; IMFMediaType encoderType = null; TantaMFDevice currentDevice = null; try { // get the current video device. currentDevice = ctlTantaVideoPicker1.CurrentDevice; if (currentDevice == null) { MessageBox.Show("No current video device. Are there any video devices on this system?"); return; } // check our output filename is correct and usable if ((textBoxCaptureFileNameAndPath == null) || (textBoxCaptureFileNameAndPath.Text.Length == 0)) { MessageBox.Show("No Capture Filename and path. Cannot continue."); return; } string pwszFileName = textBoxCaptureFileNameAndPath.Text; // check the path is rooted if (Path.IsPathRooted(pwszFileName) == false) { MessageBox.Show("No Capture Filename and path is not rooted. A full directory and path is required. Cannot continue."); return; } // create a new call back handler. This, once we get it all wired up, will act // as a pump to move the data from the source to the sink workingSourceReaderCallBackHandler = new TantaSourceReaderCallbackHandler(); // the following code will create a SourceReader which is tied to a camera on the system, // a SinkWriter which is tied to a file output and will hook up the two. Because we are using // a SourceReader and SourceWriter we do not have the usual Topology or Pipeline. The SourceReader // and SourceWriter are connected directly (input to output) in the code below and transfer their // data via the callback handler. The callback handler also requests the next sample from // the SourceReader when it has written the data to the sink. Note however it is possible // that the SourceReader can automatically bring in a Transform for format conversion. This // is done internally and you never deal with it - other than perhaps making it available // to the process if it is not globally available. // create the source reader workingSourceReader = TantaWMFUtils.CreateSourceReaderAsyncFromDevice(currentDevice, workingSourceReaderCallBackHandler); if (workingSourceReader == null) { MessageBox.Show("CreateSourceReaderAsyncFromDevice did not return a media source. Cannot continue."); return; } // open up the sink Writer workingSinkWriter = OpenSinkWriter(pwszFileName); if (workingSinkWriter == null) { MessageBox.Show("OpenSinkWriter workingSinkWriter == null. Cannot continue."); return; } // now set the source and the sink in the callback handler. It needs to know these // in order to operate workingSourceReaderCallBackHandler.SourceReader = workingSourceReader; workingSourceReaderCallBackHandler.SinkWriter = workingSinkWriter; workingSourceReaderCallBackHandler.InitForFirstSample(); workingSourceReaderCallBackHandler.SourceReaderAsyncCallBackError = HandleSourceReaderAsyncCallBackErrors; // now we configure the video source. It will probably offer a lot of different types // this example offers two modes: one mode where you choose the format and mode and // effectively just say "Use this one". The other uses the general case where we // present a list of reasonable types we can accept and then let it auto // configure itself from one of those. Of course if it autoconfigures itself we // don't know which one it has chosen. This is why, you will later see the video // source being interrogated after the configuration so we know which one we hit. if (radioButtonUseSpecified.Checked == true) { // we saved the video format container here - this is just the last one that came in if ((radioButtonUseSpecified.Tag == null) || ((radioButtonUseSpecified.Tag is TantaMFVideoFormatContainer) == false)) { MessageBox.Show("No source video device and format selected. Cannot continue."); return; } // get the container TantaMFVideoFormatContainer videoFormatCont = (radioButtonUseSpecified.Tag as TantaMFVideoFormatContainer); // configure the Source Reader to use this format hr = TantaWMFUtils.ConfigureSourceReaderWithVideoFormat(workingSourceReader, videoFormatCont); if (hr != HResult.S_OK) { // we failed MessageBox.Show("Failed on call to ConfigureSourceAsyncReaderWithVideoFormat (a), retVal=" + hr.ToString()); return; } } else { // prepare a list of subtypes we are prepared to accept from the video source // device. These will be tested in order - the first match will be used. List <Guid> subTypes = new List <Guid>(); subTypes.Add(MFMediaType.NV12); subTypes.Add(MFMediaType.YUY2); subTypes.Add(MFMediaType.UYVY); subTypes.Add(MFMediaType.RGB32); subTypes.Add(MFMediaType.RGB24); subTypes.Add(MFMediaType.IYUV); // make sure the default Media Type is one of the above video formats hr = TantaWMFUtils.ConfigureSourceReaderWithVideoFormat(workingSourceReader, subTypes, false); if (hr != HResult.S_OK) { // we failed MessageBox.Show("Failed on call to ConfigureSourceAsyncReaderWithVideoFormat (b), retVal=" + hr.ToString()); return; } } // if we get here we know the source reader now has a configured format but we might not // know which one it is. So we ask it. It will return a video type // we will use this later on to configure our sink writer. Note, we have to properly dispose // of the videoType object after we use it. hr = workingSourceReader.GetCurrentMediaType(TantaWMFUtils.MF_SOURCE_READER_FIRST_VIDEO_STREAM, out videoType); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed on call to GetCurrentMediaType, retVal=" + hr.ToString()); } // now we configure the encoder. This sets up the sink writer so that it knows what format // the output data should be written in. The format we give the writer does not // need to be the same as the format it outputs to disk - however to make life easier for ourselves // we will copy a lot of the settings from the videoType retrieved above // create a new empty media type for us to populate hr = MFExtern.MFCreateMediaType(out encoderType); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed on call to MFCreateMediaType, retVal=" + hr.ToString()); } // The major type defines the overall category of the media data. Major types include video, audio, script & etc. hr = encoderType.SetGUID(MFAttributesClsid.MF_MT_MAJOR_TYPE, MFMediaType.Video); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed setting the MF_MT_MAJOR_TYPE, retVal=" + hr.ToString()); } // The subtype GUID defines a specific media format type within a major type. For example, within video, // the subtypes include MFMediaType.H264 (MP4), MFMediaType.WMV3 (WMV), MJPEG & etc. Within audio, the // subtypes include PCM audio, Windows Media Audio 9, & etc. hr = encoderType.SetGUID(MFAttributesClsid.MF_MT_SUBTYPE, MEDIA_TYPETO_WRITE); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed setting the MF_MT_SUBTYPE, retVal=" + hr.ToString()); } // this is the approximate data rate of the video stream, in bits per second, for a video media type // in the MF.Net sample code this is 240000 but I found 2000000 to be much better. I am not sure, // at this time, how this value is derived or what the tradeoffs are. hr = encoderType.SetUINT32(MFAttributesClsid.MF_MT_AVG_BITRATE, TARGET_BIT_RATE); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed setting the MF_MT_AVG_BITRATE, retVal=" + hr.ToString()); } // populate our new encoding type with the frame size of the videoType selected earlier hr = TantaWMFUtils.CopyAttributeData(videoType, encoderType, MFAttributesClsid.MF_MT_FRAME_SIZE); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed copying the MF_MT_FRAME_SIZE, retVal=" + hr.ToString()); } // populate our new encoding type with the frame rate of the video type selected earlier hr = TantaWMFUtils.CopyAttributeData(videoType, encoderType, MFAttributesClsid.MF_MT_FRAME_RATE); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed copying the MF_MT_FRAME_RATE, retVal=" + hr.ToString()); } // populate our new encoding type with the pixel aspect ratio of the video type selected earlier hr = TantaWMFUtils.CopyAttributeData(videoType, encoderType, MFAttributesClsid.MF_MT_PIXEL_ASPECT_RATIO); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed copying the MF_MT_PIXEL_ASPECT_RATIO, retVal=" + hr.ToString()); } // populate our new encoding type with the interlace mode of the video type selected earlier hr = TantaWMFUtils.CopyAttributeData(videoType, encoderType, MFAttributesClsid.MF_MT_INTERLACE_MODE); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed copying the MF_MT_INTERLACE_MODE, retVal=" + hr.ToString()); } // add a stream to the sink writer. The encoderType 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. int sink_stream = 0; hr = workingSinkWriter.AddStream(encoderType, out sink_stream); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed adding the output stream, retVal=" + hr.ToString()); } // Windows 10, by default, provides an adequate set of codecs which the Sink Writer can // find to write out the MP4 file. This is not true on Windows 7. // If we are not on Windows 10 we register (locally) a codec // the Sink Writer can find and use. The ColorConvertDMO is supplied by // microsoft it is just not available to enumerate on Win7 etc. // Making it available locally does not require administrator privs // but only this process can see it and it disappears when the process // closes OperatingSystem os = Environment.OSVersion; int versionID = ((os.Version.Major * 10) + os.Version.Minor); if (versionID < 62) { Guid ColorConvertDMOGUID = new Guid("98230571-0087-4204-b020-3282538e57d3"); // Register the color converter DSP for this process, in the video // processor category. This will enable the sink writer to enumerate // the color converter when the sink writer attempts to match the // media types. hr = MFExtern.MFTRegisterLocalByCLSID( ColorConvertDMOGUID, MFTransformCategory.MFT_CATEGORY_VIDEO_PROCESSOR, "", MFT_EnumFlag.SyncMFT, 0, null, 0, null ); } // 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 = workingSinkWriter.SetInputMediaType(sink_stream, videoType, null); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed on calling SetInputMediaType on the writer, retVal=" + hr.ToString()); } // now we initialize the sink writer for writing. We call this method after configuring the // input streams but before we send any data to the sink writer. The underlying media sink must // have at least one input stream and we know it does because we set it up earlier hr = workingSinkWriter.BeginWriting(); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed on calling BeginWriting on the writer, retVal=" + hr.ToString()); } // Request the first video frame from the media source. The TantaSourceReaderCallbackHandler // set up earlier will be invoked and it will continue requesting and processing video // frames after that. hr = workingSourceReader.ReadSample( TantaWMFUtils.MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero ); if (hr != HResult.S_OK) { // we failed throw new Exception("Failed on calling the first ReadSample on the reader, retVal=" + hr.ToString()); } // we are ready to start, flag this buttonStartStopCapture.Text = STOP_CAPTURE; // disable our screen controls SetEnableStateOnScreenControls(false); } finally { // setting this to null will cause it to be cleaned up currentDevice = null; // close and release if (videoType != null) { Marshal.ReleaseComObject(videoType); videoType = null; } if (encoderType != null) { Marshal.ReleaseComObject(encoderType); encoderType = null; } } }
private static void InitializeSinkWriter(string outputUrl, int width, int height, int fps, out IMFSinkWriter writer, out int videoStreamIndex, out int audioStreamIndex) { Marshal.ThrowExceptionForHR((int)MFExtern.MFCreateAttributes(out IMFAttributes attributes, 0)); try { Marshal.ThrowExceptionForHR((int)attributes.SetUINT32(MFAttributesClsid.MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, 1)); Marshal.ThrowExceptionForHR((int)attributes.SetUINT32(MFAttributesClsid.MF_SINK_WRITER_DISABLE_THROTTLING, 1)); Marshal.ThrowExceptionForHR((int)MFExtern.MFCreateSinkWriterFromURL(outputUrl, null, attributes, out writer)); } finally { Marshal.ReleaseComObject(attributes); } try { // Set the video output media type. Marshal.ThrowExceptionForHR((int)MFExtern.MFCreateMediaType(out IMFMediaType videoMediaTypeOut)); try { Marshal.ThrowExceptionForHR((int)videoMediaTypeOut.SetGUID(MFAttributesClsid.MF_MT_MAJOR_TYPE, MFMediaType.Video)); Marshal.ThrowExceptionForHR((int)videoMediaTypeOut.SetGUID(MFAttributesClsid.MF_MT_SUBTYPE, MFMediaType.H264)); Marshal.ThrowExceptionForHR((int)videoMediaTypeOut.SetUINT32(MFAttributesClsid.MF_MT_AVG_BITRATE, 800000)); Marshal.ThrowExceptionForHR((int)videoMediaTypeOut.SetUINT32(MFAttributesClsid.MF_MT_INTERLACE_MODE, (int)MFVideoInterlaceMode.Progressive)); Marshal.ThrowExceptionForHR((int)MFExtern.MFSetAttributeSize(videoMediaTypeOut, MFAttributesClsid.MF_MT_FRAME_SIZE, width, height)); Marshal.ThrowExceptionForHR((int)MFExtern.MFSetAttributeRatio(videoMediaTypeOut, MFAttributesClsid.MF_MT_FRAME_RATE, fps, 1)); Marshal.ThrowExceptionForHR((int)MFExtern.MFSetAttributeRatio(videoMediaTypeOut, MFAttributesClsid.MF_MT_PIXEL_ASPECT_RATIO, 1, 1)); Marshal.ThrowExceptionForHR((int)writer.AddStream(videoMediaTypeOut, out videoStreamIndex)); } finally { Marshal.ReleaseComObject(videoMediaTypeOut); } // Set the video input media type. Marshal.ThrowExceptionForHR((int)MFExtern.MFCreateMediaType(out IMFMediaType videoMediaTypeIn)); try { Marshal.ThrowExceptionForHR((int)videoMediaTypeIn.SetGUID(MFAttributesClsid.MF_MT_MAJOR_TYPE, MFMediaType.Video)); Marshal.ThrowExceptionForHR((int)videoMediaTypeIn.SetGUID(MFAttributesClsid.MF_MT_SUBTYPE, MFMediaType.RGB565)); Marshal.ThrowExceptionForHR((int)videoMediaTypeIn.SetUINT32(MFAttributesClsid.MF_MT_INTERLACE_MODE, (int)MFVideoInterlaceMode.Progressive)); Marshal.ThrowExceptionForHR((int)MFExtern.MFSetAttributeSize(videoMediaTypeIn, MFAttributesClsid.MF_MT_FRAME_SIZE, width, height)); Marshal.ThrowExceptionForHR((int)MFExtern.MFSetAttributeRatio(videoMediaTypeIn, MFAttributesClsid.MF_MT_FRAME_RATE, fps, 1)); Marshal.ThrowExceptionForHR((int)MFExtern.MFSetAttributeRatio(videoMediaTypeIn, MFAttributesClsid.MF_MT_PIXEL_ASPECT_RATIO, 1, 1)); Marshal.ThrowExceptionForHR((int)writer.SetInputMediaType(videoStreamIndex, videoMediaTypeIn, null)); } finally { Marshal.ReleaseComObject(videoMediaTypeIn); } // Set the audio output media type. Marshal.ThrowExceptionForHR((int)MFExtern.MFCreateMediaType(out IMFMediaType audioMediaTypeOut)); try { Marshal.ThrowExceptionForHR((int)audioMediaTypeOut.SetGUID(MFAttributesClsid.MF_MT_MAJOR_TYPE, MFMediaType.Audio)); Marshal.ThrowExceptionForHR((int)audioMediaTypeOut.SetGUID(MFAttributesClsid.MF_MT_SUBTYPE, MFMediaType.AAC)); Marshal.ThrowExceptionForHR((int)audioMediaTypeOut.SetUINT32(MFAttributesClsid.MF_MT_AUDIO_BITS_PER_SAMPLE, 16)); Marshal.ThrowExceptionForHR((int)audioMediaTypeOut.SetUINT32(MFAttributesClsid.MF_MT_AUDIO_SAMPLES_PER_SECOND, 44100)); Marshal.ThrowExceptionForHR((int)audioMediaTypeOut.SetUINT32(MFAttributesClsid.MF_MT_AUDIO_NUM_CHANNELS, 2)); Marshal.ThrowExceptionForHR((int)audioMediaTypeOut.SetUINT32(MFAttributesClsid.MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 20000)); Marshal.ThrowExceptionForHR((int)writer.AddStream(audioMediaTypeOut, out audioStreamIndex)); } finally { Marshal.ReleaseComObject(audioMediaTypeOut); } // Set the audio input media type. Marshal.ThrowExceptionForHR((int)MFExtern.MFCreateMediaType(out IMFMediaType audioMediaTypeIn)); try { Marshal.ThrowExceptionForHR((int)audioMediaTypeIn.SetGUID(MFAttributesClsid.MF_MT_MAJOR_TYPE, MFMediaType.Audio)); Marshal.ThrowExceptionForHR((int)audioMediaTypeIn.SetGUID(MFAttributesClsid.MF_MT_SUBTYPE, MFMediaType.PCM)); Marshal.ThrowExceptionForHR((int)audioMediaTypeIn.SetUINT32(MFAttributesClsid.MF_MT_AUDIO_BITS_PER_SAMPLE, 16)); Marshal.ThrowExceptionForHR((int)audioMediaTypeIn.SetUINT32(MFAttributesClsid.MF_MT_AUDIO_SAMPLES_PER_SECOND, 44100)); Marshal.ThrowExceptionForHR((int)audioMediaTypeIn.SetUINT32(MFAttributesClsid.MF_MT_AUDIO_NUM_CHANNELS, 2)); Marshal.ThrowExceptionForHR((int)writer.SetInputMediaType(audioStreamIndex, audioMediaTypeIn, null)); } finally { Marshal.ReleaseComObject(audioMediaTypeIn); } } catch { Marshal.ReleaseComObject(writer); throw; } // Tell the sink writer to start accepting data. Marshal.ThrowExceptionForHR((int)writer.BeginWriting()); }
/// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+= /// <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()); } }