user2541873
user2541873

Reputation:

SinkWriter.WriteSample() fails with E_INVALIDARG

I'm using MediaFoundation with SharpDX in order to encode video files from desktop duplication frames.

I'm creating a texture and capturing the screen. Then this texture is passed to MFCreateVideoSampleFromSurface, which is called once.

/* creating the texture */
new Texture2D(/* device */,
  new Texture2DDescription {
  CpuAccessFlags = CpuAccessFlags.Read,
  BindFlags = BindFlags.None,
  Format = Format.B8G8R8A8_UNorm,
  Width = /* width */,
  Height = /* height */,
  OptionFlags = ResourceOptionFlags.None,
  MipLevels = 1,
  ArraySize = 1,
  SampleDescription = { Count = 1, Quality = 0 },
  Usage = ResourceUsage.Staging
})

/* creating the sample */
Evr.CreateVideoSampleFromSurface(SourceTexture, out /* the sample */);

Once the sample and texture are initialized I'm calling IDXGIOutputDuplication::AcquireFrame(), copying the pertinent region from the screen to the texture I created beforehand by using ID3D11DeviceContext::CopySubresourceRegion(), adjusting the sample time and calling ISinkWriter::WriteSample(), which fails with ERROR_INVALID_PARAMETER.

These are the attributes I'm passing to the SinkWriter:

using (var attrs = new MediaAttributes()) {
  attrs.Set(TranscodeAttributeKeys.TranscodeContainertype, /* container type GUID */);
  attrs.Set(SinkWriterAttributeKeys.ReadwriteEnableHardwareTransforms, 1);
  attrs.Set(SinkWriterAttributeKeys.LowLatency, true);

  if (SourceTexture != null) {
    // create and bind a DXGI device manager
    this.dxgiManager = new DXGIDeviceManager();
    this.dxgiManager.ResetDevice(SourceTexture.Device);

    attrs.Set(SinkWriterAttributeKeys.D3DManager, this.dxgiManager);
  }

  this.byteStream = new ByteStream(/* ... */);
  this.sinkWriter = MediaFactory.CreateSinkWriterFromURL(null, this.byteStream.NativePointer, attrs);

  /* ... */
}

This is how I'm initializing the input media type:

using (var inMediaType = new MediaType()) {
  inMediaType.Set(MediaTypeAttributeKeys.MajorType, MediaTypeGuids.Video);
  inMediaType.Set(MediaTypeAttributeKeys.Subtype, VideoFormatGuids.Rgb32);
  inMediaType.Set(MediaTypeAttributeKeys.InterlaceMode, (int) VideoInterlaceMode.Progressive);
  inMediaType.Set(MediaTypeAttributeKeys.FrameSize,
    ((long) frameSize.Width << 32) | (uint) frameSize.Height);
  inMediaType.Set(MediaTypeAttributeKeys.FrameRate, ((long) FrameRate << 32) | 1);
  inMediaType.Set(MediaTypeAttributeKeys.PixelAspectRatio, 1);

  this.sinkWriter.SetInputMediaType(this.streamIdx, inMediaType, null);
  this.sinkWriter.BeginWriting();
}

mftrace is showing this (full log):

6532,C24 17:03:29.01758 CMFSinkWriterDetours::WriteSample @000000001ED45D50 Stream Index 0x0, Sample @000000001ED46B30, Time 0ms, Duration 0ms, Buffers 1, Size 0B,
6532,C24 17:03:29.01759 CMFSinkWriterDetours::WriteSample @000000001ED45D50 failed hr=0x80070057 ERROR_INVALID_PARAMETER

(note: albeit duration is 0ms here due to an unrelated bug, I also tried manually specifying the sample duration with different values, which also failed with the same error)

If this is somehow relevant, trying to encode video with small screen regions (600x500, et cetera) seemed to work flawlessly 1/3 of the time. Trying to encode 1920x1080 frames in this case did not succeed in any case, which does not make any sense to me (I'm not passing any disposed resources nor I seem to be reading free'd memory)

I've also tried to set up buffers and samples manually (with MFCreateDXGISurfaceBuffer) and the outcome was the same.

This is the full source file for MediaFoundation encoding and this includes how I'm creating the textures and capturing from the screen, in the case I inadvertently omitted relevant code.

Thanks in advance.

Upvotes: 3

Views: 825

Answers (1)

user2541873
user2541873

Reputation:

It turns out that MFCreateVideoSampleFromSurface did not properly set the length of the created buffer. The way I solved this is as follows:

  1. Call MFCreateDXGISurfaceBuffer, which creates a media buffer that supports the IMF2DBuffer interface.
  2. Query the IMF2DBuffer interface and set the length of the newly-created surface buffer to the contiguous length of the IMF2DBuffer (another way would be setting the current buffer length to the maximum buffer length, and thus not requiring to query the IMF2DBuffer interface, but the canonical way to achieve this is by querying the contiguous length of the IMF2DBuffer, so I stick to that.)
  3. Call MFCreateVideoSampleFromSurface with a null surface pointer—which I found to be pointless—or simply create a IMFSample normally.
  4. Add the surface buffer to the new sample and write it.

This is how I've got it to work with C#/SharpDX:

this.sample?.Dispose();
this.buffer?.Dispose();

MediaFactory.CreateDXGISurfaceBuffer(SourceTexture.GetType().GUID,
  SourceTexture,
  0,
  new RawBool(false),
  out this.buffer);

this.sample = MediaFactory.CreateSample();
this.sample.SampleTime = time;

this.buffer.CurrentLength = this.buffer.QueryInterface<Buffer2D>().ContiguousLength;
this.sample.AddBuffer(this.buffer);

Hope it helps anyone out there using MediaFoundation. I looked everywhere on the internet and found no useful information about this problem, which is trivial, yet counter-intuitive.

Upvotes: 6

Related Questions