Здесь у меня возникает несколько потенциальных проблем. Роман упомянул два больших, поэтому я остановлюсь на них подробнее. У меня есть еще пара критических замечаний/предложений для вас.
Не использую IMFDXGIDeviceManager
Чтобы использовать аппаратное ускорение в Media Foundation, вам необходимо создать объект диспетчера устройств DirectX, либо IDirect3DDeviceManager9
для DX9 или, в вашем случае, IMFDXGIDeviceManager
для DXGI. Я настоятельно рекомендую прочитать всю документацию MSDN по этому интерфейсу. Причина, по которой это необходимо, заключается в том, что одно и то же устройство DX должно совместно использоваться всеми взаимодействующими аппаратными преобразованиями MF, поскольку всем им требуется доступ к общей памяти графического процессора, контролируемой устройством, и каждому из них требуется эксклюзивный контроль над устройством во время его работы. , поэтому нужна система блокировки. Объект диспетчера устройств обеспечивает эту систему блокировки, а также является стандартным способом предоставления устройства DX для одного или нескольких преобразований. Для DXGI вы создаете это, используя MFCreateDXGIDeviceManager
а>.
Оттуда вам нужно создать устройство DX11 и вызвать IMFDXGIDeviceManager::ResetDevice
на устройстве DX11. Затем вам нужно установить диспетчер устройств для самого Sink Writer, чего нет в приведенном выше коде. Это выполняется так:
// ... inside your InitializeSinkWriter function that you listed above
// I'm assuming you've already created and set up the DXGI device manager elsewhere
IMFDXGIDeviceManager pDeviceManager;
// Passing 3 as the argument because we're adding 3 attributes immediately, saves re-allocations
if (Succeeded(hr)) hr = (int)MFExtern.MFCreateAttributes(out attributes, 3);
if (Succeeded(hr)) hr = (int)attributes.SetUINT32(MFAttributesClsid.MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, 1);
if (Succeeded(hr)) hr = (int)attributes.SetUINT32(MFAttributesClsid.MF_LOW_LATENCY, 1);
// Here's the key piece!
if (Succeeded(hr)) hr = (int)attributes.SetUnknown(MFAttributesClsid.MF_SINK_WRITER_D3D_MANAGER, pDeviceManager);
// Create the sink writer
if (Succeeded(hr)) hr = (int)MFExtern.MFCreateSinkWriterFromURL(outputFile, null, attributes, out sinkWriter);
Это фактически активирует поддержку D3D11 для аппаратного кодировщика и позволит ему читать Texture2D
, который вы передаете. Стоит отметить, что MF_SINK_WRITER_D3D_MANAGER
работает как для диспетчеров устройств DX9, так и для DXGI. <ч/>
Кодер буферизует несколько IMFSample
экземпляров одной и той же текстуры
Это также является потенциальной причиной вашей проблемы - по крайней мере, это приведет к большому количеству непреднамеренного поведения, даже если оно не является причиной очевидной проблемы. Основываясь на комментарии Романа, многие кодировщики будут буферизовать несколько кадров как часть процесса кодирования. Вы не видите такого поведения при использовании Sink Writer, потому что он обрабатывает всю детальную работу за вас. Однако то, что вы пытаетесь выполнить (т. е. отправлять текстуры D3D11 в качестве входных кадров), находится на достаточно низком уровне, и вам приходится беспокоиться о внутренних деталях кодировщика MFT, используемого Sink Writer.
Большинство видеокодировщиков MFT используют внутренний буфер определенного размера для хранения последних N образцов, предоставленных через IMFTransform::ProcessInput
. Это имеет побочный эффект, заключающийся в том, что несколько выборок должны быть предоставлены в качестве входных данных, прежде чем будет сгенерирован какой-либо вывод. Видеокодерам необходим доступ к нескольким образцам по порядку, потому что они используют последующие кадры, чтобы определить, как кодировать текущий кадр. Другими словами, если декодер работает с кадром 0, ему может потребоваться также просмотреть кадры 1, 2 и 3. С технической точки зрения это связано с такими вещами, как межкадровое предсказание и оценка движения. Как только кодировщик закончит обработку самой старой выборки, он создаст выходной буфер (еще один объект IMFSample
, но на этот раз на стороне вывода через IMFTransform::ProcessOutput
), затем отбрасывает входной образец, над которым он работал (вызывая IUnknown::Release
), затем запрашивает дополнительные входные данные и в конечном итоге переходит к к следующему кадру. Подробнее об этом процессе можно прочитать в статье MSDN Обработка данных в кодировщике
Это означает, как намекнул Роман, что вы инкапсулируете ID3D11Texture2D
внутри IMFMediaBuffer
внутри IMFSample
, а затем передаете это Sink Writer. Этот образец, вероятно, буферизуется кодировщиком как часть процесса кодирования. По мере работы энкодера содержимое этого Texture2D
, вероятно, меняется, что может вызвать множество проблем. Даже если бы это не вызвало программных ошибок, это, безусловно, привело бы к очень странным закодированным видеовыходам. Представьте себе, что кодировщик пытается предсказать, как визуальное содержимое одного кадра изменится в следующем кадре, а затем фактическое визуальное содержимое обоих кадров обновляется из-под кодировщика!
Эта конкретная проблема возникает из-за того, что кодировщик имеет только ссылку указателя на ваш экземпляр IMFSample
, который в конечном итоге является просто указателем на ваш объект ID3D11Texture2D
, и этот объект является своего рода ссылкой указателя на изменяемую графическую память. В конечном счете, содержимое этой графической памяти изменяется из-за какой-то другой части вашей программы, но поскольку всегда обновляется одна и та же текстура графического процессора, каждый отдельный образец, который вы отправляете кодировщику, указывает на одну и ту же единственную текстуру. Это означает, что всякий раз, когда вы обновляете текстуру, изменяя память графического процессора, все активные объекты IMFSample
будут отражать эти изменения, поскольку все они фактически указывают на одну и ту же текстуру графического процессора.
Чтобы исправить это, вам нужно выделить несколько объектов ID3D11Texture2D
, чтобы вы могли соединить одну текстуру с одним IMFSample
при предоставлении его Sink Writer. Это решит проблему, когда все сэмплы указывают на одну и ту же текстуру графического процессора, заставив каждый сэмпл указывать на уникальную текстуру. Однако вы не обязательно будете знать, сколько текстур вам нужно создать, поэтому самый безопасный способ справиться с этим — написать собственный распределитель текстур. Это все еще можно сделать в C#, для чего это стоит, MediaFoundation.NET имеет определенные интерфейсы, которые вам нужно будет использовать.
Распределитель должен поддерживать список «свободных» SharpDX.Texture2D
объектов — тех, которые в данный момент не используются модулем записи/кодировщиком приемника. Ваша программа должна иметь возможность запрашивать новые объекты текстуры из распределителя, и в этом случае она либо вернет объект из списка свободных, либо создаст новую текстуру для удовлетворения запроса.
Следующая проблема заключается в том, чтобы узнать, когда объект IMFSample
был отброшен кодировщиком, чтобы вы могли добавить прикрепленную текстуру обратно в список свободных. Как оказалось, функция MFCreateVideoSampleFromSurface
, которую вы сейчас используете, выделяет образцы, реализующие IMFTrackedSample
. Вам понадобится этот интерфейс, чтобы получать уведомления об освобождении образца, чтобы вы могли вернуть Texture2D
объектов.
Хитрость заключается в том, что вы должны сообщить образцу, что вы являетесь распределителем. Во-первых, ваш класс распределителя должен реализовать IMFAsyncCallback
. Если вы установите свой класс распределителя в образце через IMFTrackedSample::SetAllocator
, IMFAsyncCallback::Invoke
будет вызываться метод с IMFAsyncResult
передается в качестве аргумента всякий раз, когда кодировщик выпускает образец. Вот общий пример того, как может выглядеть этот класс распределителя.
sealed class TextureAllocator : IMFAsyncCallback, IDisposable
{
private ConcurrentStack<SharpDX.Direct3D11.Texture2D> m_freeStack;
private static readonly Guid s_IID_ID3D11Texture2D = new Guid("6f15aaf2-d208-4e89-9ab4-489535d34f9c");
// If all textures are the exact same size and color format,
// consider making those parameters private class members and
// requiring they be specified as arguments to the constructor.
public TextureAllocator()
{
m_freeStack = new ConcurrentStack<SharpDX.Direct3D11.Texture2D>();
}
private bool disposedValue = false;
private void Dispose(bool disposing)
{
if(!disposedValue)
{
if(disposing)
{
// Dispose managed resources here
}
if(m_freeStack != null)
{
SharpDX.Direct3D11.Texture2D texture;
while(m_freeStack.TryPop(out texture))
{
texture.Dispose();
}
m_freeStack = null;
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~TextureAllocator()
{
Dispose(false);
}
private SharpDX.Direct3D11.Texture2D InternalAllocateNewTexture()
{
// Allocate a new texture with your format, size, etc here.
}
public SharpDX.Direct3D11.Texture2D AllocateTexture()
{
SharpDX.Direct3D11.Texture2D existingTexture;
if(m_freeStack.TryPop(out existingTexture))
{
return existingTexture;
}
else
{
return InternalAllocateNewTexture();
}
}
public IMFSample CreateSampleAndAllocateTexture()
{
IMFSample pSample;
IMFTrackedSample pTrackedSample;
HResult hr;
// Create the video sample. This function returns an IMFTrackedSample per MSDN
hr = MFExtern.MFCreateVideoSampleFromSurface(null, out pSample);
MFError.ThrowExceptionForHR(hr);
// Query the IMFSample to see if it implements IMFTrackedSample
pTrackedSample = pSample as IMFTrackedSample;
if(pTrackedSample == null)
{
// Throw an exception if we didn't get an IMFTrackedSample
// but this shouldn't happen in practice.
throw new InvalidCastException("MFCreateVideoSampleFromSurface returned a sample that did not implement IMFTrackedSample");
}
// Use our own class to allocate a texture
SharpDX.Direct3D11.Texture2D availableTexture = AllocateTexture();
// Convert the texture's native ID3D11Texture2D pointer into
// an IUnknown (represented as as System.Object)
object texNativeObject = Marshal.GetObjectForIUnknown(availableTexture.NativePointer);
// Create the media buffer from the texture
IMFMediaBuffer p2DBuffer;
hr = MFExtern.MFCreateDXGISurfaceBuffer(s_IID_ID3D11Texture2D, texNativeObject, 0, false, out p2DBuffer);
// Release the object-as-IUnknown we created above
COMBase.SafeRelease(texNativeObject);
// If media buffer creation failed, throw an exception
MFError.ThrowExceptionForHR(hr);
// Set the owning instance of this class as the allocator
// for IMFTrackedSample to notify when the sample is released
pTrackedSample.SetAllocator(this, null);
// Attach the created buffer to the sample
pTrackedSample.AddBuffer(p2DBuffer);
return pTrackedSample;
}
// This is public so any textures you allocate but don't make IMFSamples
// out of can be returned to the allocator manually.
public void ReturnFreeTexture(SharpDX.Direct3D11.Texture2D freeTexture)
{
m_freeStack.Push(freeTexture);
}
// IMFAsyncCallback.GetParameters
// This is allowed to return E_NOTIMPL as a way of specifying
// there are no special parameters.
public HResult GetParameters(out MFAsync pdwFlags, out MFAsyncCallbackQueue pdwQueue)
{
pdwFlags = MFAsync.None;
pdwQueue = MFAsyncCallbackQueue.Standard;
return HResult.E_NOTIMPL;
}
public HResult Invoke(IMFAsyncResult pResult)
{
object pUnkObject;
IMFSample pSample = null;
IMFMediaBuffer pBuffer = null;
IMFDXGIBuffer pDXGIBuffer = null;
// Get the IUnknown out of the IMFAsyncResult if there is one
HResult hr = pResult.GetObject(out pUnkObject);
if(Succeeded(hr))
{
pSample = pUnkObject as IMFSample;
}
if(pSample != null)
{
// Based on your implementation, there should only be one
// buffer attached to one sample, so we can always grab the
// first buffer. You could add some error checking here to make
// sure the sample has a buffer count that is 1.
hr = pSample.GetBufferByIndex(0, out pBuffer);
}
if(Succeeded(hr))
{
// Query the IMFMediaBuffer to see if it implements IMFDXGIBuffer
pDXGIBuffer = pBuffer as IMFDXGIBuffer;
}
if(pDXGIBuffer != null)
{
// Got an IMFDXGIBuffer, so we can extract the internal
// ID3D11Texture2D and make a new SharpDX.Texture2D wrapper.
hr = pDXGIBuffer.GetResource(s_IID_ID3D11Texture2D, out pUnkObject);
}
if(Succeeded(hr))
{
// If we got here, pUnkObject is the native D3D11 Texture2D as
// a System.Object, but it's unlikely you have an interface
// definition for ID3D11Texture2D handy, so we can't just cast
// the object to the proper interface.
// Happily, SharpDX supports wrapping System.Object within
// SharpDX.ComObject which makes things pretty easy.
SharpDX.ComObject comWrapper = new SharpDX.ComObject(pUnkObject);
// If this doesn't work, or you're using something like SlimDX
// which doesn't support object wrapping the same way, the below
// code is an alternative way.
/*
IntPtr pD3DTexture2D = Marshal.GetIUnknownForObject(pUnkObject);
// Create your wrapper object here, like this for SharpDX
SharpDX.ComObject comWrapper = new SharpDX.ComObject(pD3DTexture2D);
// or like this for SlimDX
SlimDX.Direct3D11.Texture2D.FromPointer(pD3DTexture2D);
Marshal.Release(pD3DTexture2D);
*/
// You might need to query comWrapper for a SharpDX.DXGI.Resource
// first, then query that for the SharpDX.Direct3D11.Texture2D.
SharpDX.Direct3D11.Texture2D texture = comWrapper.QueryInterface<SharpDX.Direct3D11.Texture2D>();
if(texture != null)
{
// Now you can add "texture" back to the allocator's free list
ReturnFreeTexture(texture);
}
}
}
}
Настройка MF_SA_D3D_AWARE
для типа носителя ввода Sink Writer
Я не думаю, что это вызывает плохое HRESULT
, которое вы получаете, но, тем не менее, это неправильно. MF_SA_D3D_AWARE
(и его аналог DX11, MF_SA_D3D11_AWARE
) — это атрибуты, установленные объектом IMFTransform
, чтобы сообщить вам, что преобразование поддерживает графическое ускорение через DX9 или DX11 соответственно. Нет необходимости устанавливать это для типа носителя ввода Sink Writer. <ч/>
No SafeRelease
on texNativeObject
Я бы рекомендовал позвонить COMBase.SafeRelease()
по телефону texNativeObject
, иначе может произойти утечка памяти. Это, или вы продлите время жизни этого COM-объекта без необходимости, пока сборщик мусора не очистит счетчик ссылок для вас
Ненужный кастинг
Это часть вашего кода сверху:
buffer = MFVideoEncoderST.ReinterpretCast<IMF2DBuffer,IMFMediaBuffer>(p2Dbuffer);
int length=0;
if (Succeeded(hr)) hr = (int)p2Dbuffer.GetContiguousLength(out length);
if (Succeeded(hr)) hr = (int)buffer.SetCurrentLength(length);
Я не уверен, что делает ваша функция ReinterpretCast
, но если вам делает нужно выполнить приведение типа QueryInterface
в C#, вы можете просто использовать оператор as
или обычное приведение.
// pMediaBuffer is of type IMFMediaBuffer and has been created elsewhere
IMF2DBuffer p2DBuffer = pMediaBuffer as IMF2DBuffer;
if(p2DBuffer != null)
{
// pMediaBuffer is an IMFMediaBuffer that also implements IMF2DBuffer
}
else
{
// pMediaBuffer does not implement IMF2DBuffer
}
person
ozeanix
schedule
07.06.2017
0x887A0005
означает, что экземпляр устройства GPU был приостановлен. Используйте GetDeviceRemovedReason, чтобы определить соответствующее действие. Ваш код, похоже, не запускает действительный сеанс аппаратного кодирования, но я бы сказал, однако, что реальная проблема заключается в текстуре вашего кадра - ее свойства и/или время жизни несовместимы с требованиями кодировщика. - person Roman R.   schedule 07.06.2017