Cómo escribir un moderador de EVR
[El componente descrito en esta página, Enhanced Video Renderer es una característica heredada. Se ha reemplazado por Simple Video Renderer (SVR) que se expone mediante los componentes MediaPlayer y IMFMediaEngine. Para reproducir contenido de vídeo, debe enviar datos a uno de estos componentes y permitirles crear instancias del nuevo representador de vídeo. Estos componentes se han optimizado para Windows 10 y Windows 11. Microsoft recomienda encarecidamente que el nuevo código use MediaPlayer o las API de IMFMediaEngine de nivel inferior para reproducir elementos multimedia de vídeo en Windows en lugar de EVR siempre que sea posible. Microsoft sugiere que el código existente que usa las API heredadas se reescriba para usar las nuevas API si es posible].
En este artículo se describe cómo escribir un moderador personalizado para el representador de vídeo mejorado (EVR). Un moderador personalizado se puede usar con DirectShow y Media Foundation. Las interfaces y el modelo de objetos son los mismos para ambas tecnologías, aunque la secuencia exacta de operaciones puede variar.
El código de ejemplo de este tema se adapta del ejemplo EVRPresenter, que se proporciona en Windows SDK.
Este tema contiene las siguientes secciones:
- Requisitos previos
- Modelo de objetos del moderador
- Negociación de formatos
- Administración del dispositivo de Direct3D
- Procesamiento de la salida
- Avance en los fotogramas
- Establecer el moderador en el EVR
- Temas relacionados
Requisitos previos
Antes de escribir un moderador personalizado, debe estar familiarizado con las siguientes tecnologías:
- Representador de vídeo mejorado. Consulte Representador de vídeo mejorado.
- Gráficos de Direct3D. No es necesario comprender los gráficos 3D para escribir un moderador, pero debe saber cómo crear un dispositivo de Direct3D y administrar superficies de Direct3D. Si no está familiarizado con Direct3D, lea las secciones "Dispositivos de Direct3D" y "Recursos de Direct3D" en la documentación del SDK de gráficos de DirectX.
- DirectShow filtra gráficos o la canalización de Media Foundation, en función de la tecnología que usará la aplicación para representar el vídeo.
- Transformaciones de Media Foundation. El mezclador de EVR es una transformación de Media Foundation y el moderador llama a métodos directamente en el mezclador.
- Implementación de objetos COM. El moderador es un objeto COM en proceso y sin subprocesos.
Modelo de objetos del moderador
Esta sección contiene información general sobre el modelo de objetos y las interfaces del moderador.
Flujo de datos dentro del EVR
El EVR usa dos componentes del complemento para representar vídeo: el mezclador y el moderador. El mezclador combina las secuencias de vídeo y deshace el entrelazado del vídeo si es necesario. El moderador dibuja (o presenta) el vídeo en la pantalla y programa cuándo se dibuja cada fotograma. Las aplicaciones pueden reemplazar cualquiera de estos objetos por una implementación personalizada.
El EVR tiene uno o varios flujos de entrada y el mezclador tiene un número correspondiente de flujos de entrada. La secuencia 0 es siempre la secuencia de referencia. Las otras secuencias son subsecuencias, donde el mezclador realiza una combinación alfa en la secuencia de referencia. La secuencia de referencia determina la velocidad de fotogramas maestra para el vídeo compuesto. Para cada fotograma de referencia, el mezclador toma el fotograma más reciente de cada subsecuencia, realiza una combinación alfa en el fotograma de referencia y genera un único fotograma compuesto. El mezclador también deshace el entrelazado y realiza la conversión de color de YUV a RGB si es necesario. El EVR siempre inserta el mezclador en la canalización de vídeo, independientemente del número de flujos de entrada o el formato de vídeo. En la imagen siguiente se muestra este proceso.
El moderador realiza las siguientes tareas:
- Establece el formato de salida en el mezclador. Antes de que comience el streaming, el moderador establece un tipo de elemento multimedia en el flujo de salida del mezclador. Este tipo de elemento multimedia define el formato de la imagen compuesta.
- Crea el dispositivo de Direct3D.
- Asigna superficies de Direct3D. El mezclador realiza la transferencia de bloques de bits de los fotogramas compuestos en estas superficies.
- Obtiene la salida del mezclador.
- Programa cuándo se presentan los fotogramas. El EVR proporciona el reloj de presentación y el moderador programa los fotogramas según este reloj.
- Presenta cada fotograma mediante Direct3D.
- Realiza el avance en los fotogramas y la limpieza de fotogramas.
Estados del moderador
En cualquier momento, el moderador se encuentra en uno de los siguientes estados:
- Iniciado. El reloj de presentación del EVR está en ejecución. El moderador programa fotogramas de vídeo para su presentación a medida que llegan.
- En pausa. El reloj de presentación está suspendido. El moderador no presenta ninguna muestra nueva, pero mantiene su cola de muestras programadas. Si se reciben nuevas muestras, el moderador las agrega a la cola.
- Detenido. El reloj de presentación se encuentra detenido. El moderador descarta las muestras programadas.
- Apagado.. El moderador libera los recursos relacionados con el streaming, como las superficies de Direct3D. Este es el estado inicial del moderador y el estado final antes de que se destruya el moderador.
En el código de ejemplo de este tema, el estado del moderador se representa mediante una enumeración:
enum RENDER_STATE
{
RENDER_STATE_STARTED = 1,
RENDER_STATE_STOPPED,
RENDER_STATE_PAUSED,
RENDER_STATE_SHUTDOWN, // Initial state.
};
Algunas operaciones no son válidas mientras el moderador está en estado apagado. El código de ejemplo comprueba este estado llamando a un método auxiliar:
HRESULT CheckShutdown() const
{
if (m_RenderState == RENDER_STATE_SHUTDOWN)
{
return MF_E_SHUTDOWN;
}
else
{
return S_OK;
}
}
Interfaces del moderador
Se requiere un moderador para implementar las interfaces siguientes:
Interfaz | Descripción |
---|---|
IMFClockStateSink | Notifica al moderador cuándo cambia el estado del reloj del EVR. Consulte Implementación de IMFClockStateSink. |
IMFGetService | Proporciona una manera de que la aplicación y otros componentes de la canalización obtengan interfaces del moderador. |
IMFTopologyServiceLookupClient | Permite al moderador obtener interfaces del EVR o del mezclador. Consulte Implementación de IMFTopologyServiceLookupClient. |
IMFVideoDeviceID | Garantiza que el moderador y el mezclador usen tecnologías compatibles. Consulte Implementación de IMFVideoDeviceID. |
IMFVideoPresenter | Procesa los mensajes del EVR. Consulte Implementación de IMFVideoPresenter. |
Las siguientes interfaces son opcionales:
Interfaz | Descripción |
---|---|
IEVRTrustedVideoPlugin | Permite al moderador trabajar con elementos multimedia protegidos. Implemente esta interfaz si el moderador es un componente de confianza diseñado para funcionar en la ruta de acceso multimedia protegida (PMP). |
IMFRateSupport | Informa del intervalo de velocidades de reproducción que admite el moderador. Consulte Implementación de IMFRateSupport. |
IMFVideoPositionMapper | Asigna coordenadas en el fotograma de vídeo de salida a las coordenadas del fotograma de vídeo de entrada. |
IQualProp | Informa sobre la información de rendimiento. El EVR utiliza esta información para la administración del control de calidad. Esta interfaz se documenta en el SDK de DirectShow. |
También puede proporcionar interfaces para que la aplicación se comunique con el moderador. El moderador estándar implementa la interfaz IMFVideoDisplayControl para este propósito. Puede implementar esta interfaz o definir la suya propia. La aplicación obtiene interfaces del moderador llamando a IMFGetService::GetService en el EVR. Cuando el GUID de servicio es MR_VIDEO_RENDER_SERVICE, el EVR pasa la solicitud GetService al moderador.
Implementación de IMFVideoDeviceID
La interfaz IMFVideoDeviceID contiene un método, GetDeviceID, que devuelve un GUID de dispositivo. El GUID de dispositivo garantiza que el moderador y el mezclador usen tecnologías compatibles. Si los GUID de dispositivo no coinciden, el EVR no se puede inicializar.
El mezclador estándar y el moderador usan Direct3D 9, con el GUID de dispositivo igual a IID_IDirect3DDevice9. Si piensa usar el moderador personalizado con el mezclador estándar, el GUID de dispositivo del moderador debe ser IID_IDirect3DDevice9. Si reemplaza ambos componentes, podría definir un nuevo GUID de dispositivo. Para el resto de este artículo, se supone que el moderador usa Direct3D 9. Esta es la implementación estándar de GetDeviceID:
HRESULT EVRCustomPresenter::GetDeviceID(IID* pDeviceID)
{
if (pDeviceID == NULL)
{
return E_POINTER;
}
*pDeviceID = __uuidof(IDirect3DDevice9);
return S_OK;
}
El método debe realizarse correctamente incluso cuando se apaga el moderador.
Implementación de IMFTopologyServiceLookupClient
La interfaz IMFTopologyServiceLookupClient permite al moderador obtener punteros de interfaz del EVR y del mezclador de la siguiente manera:
- Cuando el EVR inicializa el moderador, llama al método IMFTopologyServiceLookupClient::InitServicePointers del moderador. El argumento es un puntero de la interfaz IMFTopologyServiceLookup del EVR.
- El moderador llama a IMFTopologyServiceLookup::LookupService para obtener punteros de interfaz desde el EVR o el mezclador.
El método LookupService es similar al método IMFGetService::GetService. Ambos métodos toman un GUID de servicio y un identificador de interfaz (IID) como entrada, pero LookupService devuelve una matriz de punteros de interfaz, mientras que GetService devuelve un único puntero. Sin embargo, en la práctica, siempre puede establecer el tamaño de la matriz en 1. El objeto consultado depende del GUID de servicio:
- Si el GUID de servicio es MR_VIDEO_RENDER_SERVICE, se consulta el EVR.
- Si el GUID de servicio es MR_VIDEO_MIXER_SERVICE, se consulta el mezclador.
En la implementación de InitServicePointers, obtenga las siguientes interfaces del EVR:
Interfaz del EVR | Descripción |
---|---|
IMediaEventSink | Proporciona una manera de que el moderador envíe mensajes al EVR. Esta interfaz se define en el SDK de DirectShow, por lo que los mensajes siguen el patrón de eventos de DirectShow, no los eventos de Media Foundation. |
IMFClock | Representa el reloj del EVR. El moderador usa esta interfaz para programar muestras para la presentación. El EVR se puede ejecutar sin un reloj, por lo que es posible que esta interfaz no esté disponible. Si no es así, omita el código de error de LookupService. El reloj también implementa la interfaz IMFTimer. En la canalización de Media Foundation, el reloj implementa la interfaz IMFPresentationClock. No implementa esta interfaz en DirectShow. |
Obtenga las siguientes interfaces del mezclador:
Interfaz del mezclador | Descripción |
---|---|
IMFTransform | Permite al moderador comunicarse con el mezclador. |
IMFVideoDeviceID | Permite al moderador validar el GUID de dispositivo del mezclador. |
El código siguiente implementa el método InitServicePointers:
HRESULT EVRCustomPresenter::InitServicePointers(
IMFTopologyServiceLookup *pLookup
)
{
if (pLookup == NULL)
{
return E_POINTER;
}
HRESULT hr = S_OK;
DWORD dwObjectCount = 0;
EnterCriticalSection(&m_ObjectLock);
// Do not allow initializing when playing or paused.
if (IsActive())
{
hr = MF_E_INVALIDREQUEST;
goto done;
}
SafeRelease(&m_pClock);
SafeRelease(&m_pMixer);
SafeRelease(&m_pMediaEventSink);
// Ask for the clock. Optional, because the EVR might not have a clock.
dwObjectCount = 1;
(void)pLookup->LookupService(
MF_SERVICE_LOOKUP_GLOBAL, // Not used.
0, // Reserved.
MR_VIDEO_RENDER_SERVICE, // Service to look up.
IID_PPV_ARGS(&m_pClock), // Interface to retrieve.
&dwObjectCount // Number of elements retrieved.
);
// Ask for the mixer. (Required.)
dwObjectCount = 1;
hr = pLookup->LookupService(
MF_SERVICE_LOOKUP_GLOBAL, 0,
MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_pMixer), &dwObjectCount
);
if (FAILED(hr))
{
goto done;
}
// Make sure that we can work with this mixer.
hr = ConfigureMixer(m_pMixer);
if (FAILED(hr))
{
goto done;
}
// Ask for the EVR's event-sink interface. (Required.)
dwObjectCount = 1;
hr = pLookup->LookupService(
MF_SERVICE_LOOKUP_GLOBAL, 0,
MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_pMediaEventSink),
&dwObjectCount
);
if (FAILED(hr))
{
goto done;
}
// Successfully initialized. Set the state to "stopped."
m_RenderState = RENDER_STATE_STOPPED;
done:
LeaveCriticalSection(&m_ObjectLock);
return hr;
}
Cuando los punteros de interfaz obtenidos de LookupService ya no son válidos, el EVR llama a IMFTopologyServiceLookupClient::ReleaseServicePointers. Dentro de este método, libere todos los punteros de interfaz y establezca el estado del moderador en apagado:
HRESULT EVRCustomPresenter::ReleaseServicePointers()
{
// Enter the shut-down state.
EnterCriticalSection(&m_ObjectLock);
m_RenderState = RENDER_STATE_SHUTDOWN;
LeaveCriticalSection(&m_ObjectLock);
// Flush any samples that were scheduled.
Flush();
// Clear the media type and release related resources.
SetMediaType(NULL);
// Release all services that were acquired from InitServicePointers.
SafeRelease(&m_pClock);
SafeRelease(&m_pMixer);
SafeRelease(&m_pMediaEventSink);
return S_OK;
}
El EVR llama a ReleaseServicePointers por diversos motivos, entre los que se incluyen:
- Desconexión o reconexión de pins (DirectShow) o adición o eliminación de receptores de secuencias (Media Foundation).
- Cambiar el formato.
- Establecer un nuevo reloj.
- Apagado final del EVR.
Durante la vigencia del moderador, el EVR podría llamar a InitServicePointers y ReleaseServicePointers varias veces.
Implementación de IMFVideoPresenter
La interfaz IMFVideoPresenter hereda IMFClockStateSink y agrega dos métodos:
Method | Descripción |
---|---|
GetCurrentMediaType | Devuelve el tipo de elemento multimedia de los fotogramas de vídeo compuestos. |
ProcessMessage | Indica al moderador que realice varias acciones. |
El método GetCurrentMediaType devuelve el tipo de elemento multimedia del moderador. (Para obtener más información sobre cómo establecer el tipo de elemento multimedia, consulte Negociación de formatos). El tipo de elemento multimedia se devuelve como un puntero de interfaz IMFVideoMediaType. En el ejemplo siguiente se supone que el moderador almacena el tipo de elemento multimedia como un puntero IMFMediaType. Para obtener la interfaz IMFVideoMediaType del tipo de elemento multimedia, llame a QueryInterface:
HRESULT EVRCustomPresenter::GetCurrentMediaType(
IMFVideoMediaType** ppMediaType
)
{
HRESULT hr = S_OK;
if (ppMediaType == NULL)
{
return E_POINTER;
}
*ppMediaType = NULL;
EnterCriticalSection(&m_ObjectLock);
hr = CheckShutdown();
if (FAILED(hr))
{
goto done;
}
if (m_pMediaType == NULL)
{
hr = MF_E_NOT_INITIALIZED;
goto done;
}
hr = m_pMediaType->QueryInterface(IID_PPV_ARGS(ppMediaType));
done:
LeaveCriticalSection(&m_ObjectLock);
return hr;
}
El método ProcessMessage es el mecanismo principal para que el EVR se comunique con el moderador. Se definen los siguientes mensajes. Los detalles de la implementación de cada mensaje se proporcionan en el resto de este tema.
Mensaje | Descripción |
---|---|
MFVP_MESSAGE_INVALIDATEMEDIATYPE | El tipo de elemento multimedia de salida del mezclador no es válido. El moderador debe negociar un nuevo tipo de elemento multimedia con el mezclador. Consulte Negociación de formatos. |
MFVP_MESSAGE_BEGINSTREAMING | Se ha iniciado el streaming. Este mensaje no requiere ninguna acción concreta, pero puede usarlo para asignar recursos. |
MFVP_MESSAGE_ENDSTREAMING | El streaming ha finalizado. Libere los recursos asignados en respuesta al mensaje MFVP_MESSAGE_BEGINSTREAMING. |
MFVP_MESSAGE_PROCESSINPUTNOTIFY | El mezclador ha recibido una nueva muestra de entrada y puede generar un nuevo fotograma de salida. El moderador debe llamar a IMFTransform::ProcessOutput en el mezclador. Consulte Procesamiento de la salida. |
MFVP_MESSAGE_ENDOFSTREAM | La presentación ha finalizado. Consulte Fin de la secuencia. |
MFVP_MESSAGE_FLUSH | El EVR vacía los datos en su canalización de representación. El moderador debe descartar los fotogramas de vídeo programados para la presentación. |
MFVP_MESSAGE_STEP | Solicita al moderador que avance N fotogramas. El moderador debe descartar los siguientes N-1 fotogramas y mostrar el fotograma número N. Consulte Avance en los fotogramas. |
MFVP_MESSAGE_CANCELSTEP | Cancela el avance en los fotogramas. |
Implementación de IMFClockStateSink
El moderador debe implementar la interfaz IMFClockStateSink como parte de su implementación de IMFVideoPresenter, que hereda IMFClockStateSink . El EVR usa esta interfaz para notificar al moderador cada vez que cambia el estado del reloj del EVR. Para obtener más información sobre los estados del reloj, consulte Reloj de presentación.
Estas son algunas directrices para implementar los métodos en esta interfaz. Todos los métodos deben producir un error si el moderador está apagado.
Method | Descripción |
---|---|
OnClockStart |
|
OnClockStop |
|
OnClockPause | Establezca el estado del moderador en pausa. |
OnClockRestart | Trate lo mismo que OnClockStart, pero no vacíe la cola de muestras. |
OnClockSetRate |
|
Implementación de IMFRateSupport
Para admitir velocidades de reproducción distintas de 1×, el moderador debe implementar la interfaz IMFRateSupport. Estas son algunas directrices para implementar los métodos en esta interfaz. Todos los métodos deben producir un error después de apagar el moderador. Para obtener más información sobre esta interfaz, consulte Control de velocidad.
Valor | Descripción |
---|---|
GetSlowestRate | Devuelve cero para indicar que no hay ninguna velocidad de reproducción mínima. |
GetFastestRate | Para la reproducción no debilitada, la velocidad de reproducción no debe superar la frecuencia de actualización del monitor: velocidad máxima = frecuencia de actualización (Hz) / velocidad de fotogramas por segundo (fps). La velocidad de fotogramas de vídeo se especifica en el tipo de elemento multimedia del moderador. Para la reproducción debilitada, la velocidad de reproducción no está delimitada; devuelve el valor FLT_MAX. En la práctica, el origen y el descodificador serán los factores de limitación durante la reproducción debilitada. Para la reproducción inversa, devuelve el negativo de la velocidad máxima. |
IsRateSupported | Devuelve MF_E_UNSUPPORTED_RATE si el valor absoluto de flRate supera la velocidad de reproducción máxima del moderador. Calcule la velocidad máxima de reproducción tal como se describe en GetFastestRate. |
En el ejemplo siguiente se muestra cómo implementar el método GetFastestRate:
float EVRCustomPresenter::GetMaxRate(BOOL bThin)
{
// Non-thinned:
// If we have a valid frame rate and a monitor refresh rate, the maximum
// playback rate is equal to the refresh rate. Otherwise, the maximum rate
// is unbounded (FLT_MAX).
// Thinned: The maximum rate is unbounded.
float fMaxRate = FLT_MAX;
MFRatio fps = { 0, 0 };
UINT MonitorRateHz = 0;
if (!bThin && (m_pMediaType != NULL))
{
GetFrameRate(m_pMediaType, &fps);
MonitorRateHz = m_pD3DPresentEngine->RefreshRate();
if (fps.Denominator && fps.Numerator && MonitorRateHz)
{
// Max Rate = Refresh Rate / Frame Rate
fMaxRate = (float)MulDiv(
MonitorRateHz, fps.Denominator, fps.Numerator);
}
}
return fMaxRate;
}
En el ejemplo anterior se llama a un método auxiliar, GetMaxRate, para calcular la velocidad máxima de reproducción hacia delante:
En el ejemplo siguiente se muestra cómo implementar el método IsRateSupported:
HRESULT EVRCustomPresenter::IsRateSupported(
BOOL bThin,
float fRate,
float *pfNearestSupportedRate
)
{
EnterCriticalSection(&m_ObjectLock);
float fMaxRate = 0.0f;
float fNearestRate = fRate; // If we support fRate, that is the nearest.
HRESULT hr = CheckShutdown();
if (FAILED(hr))
{
goto done;
}
// Find the maximum forward rate.
// Note: We have no minimum rate (that is, we support anything down to 0).
fMaxRate = GetMaxRate(bThin);
if (fabsf(fRate) > fMaxRate)
{
// The (absolute) requested rate exceeds the maximum rate.
hr = MF_E_UNSUPPORTED_RATE;
// The nearest supported rate is fMaxRate.
fNearestRate = fMaxRate;
if (fRate < 0)
{
// Negative for reverse playback.
fNearestRate = -fNearestRate;
}
}
// Return the nearest supported rate.
if (pfNearestSupportedRate != NULL)
{
*pfNearestSupportedRate = fNearestRate;
}
done:
LeaveCriticalSection(&m_ObjectLock);
return hr;
}
Envío de eventos al EVR
El moderador debe notificar al EVR de varios eventos. Para ello, usa la interfaz IMediaEventSink del EVR, obtenida cuando el EVR llama al método IMFTopologyServiceLookupClient::InitServicePointers del moderador. (La interfaz IMediaEventSink es originalmente una interfaz de DirectShow, pero se usa en el EVR de DirectShow y Media Foundation). En el código siguiente se muestra cómo enviar un evento al EVR:
// NotifyEvent: Send an event to the EVR through its IMediaEventSink interface.
void NotifyEvent(long EventCode, LONG_PTR Param1, LONG_PTR Param2)
{
if (m_pMediaEventSink)
{
m_pMediaEventSink->Notify(EventCode, Param1, Param2);
}
}
En la tabla siguiente se enumeran los eventos que envía el moderador, junto con los parámetros del evento.
Evento | Descripción |
---|---|
EC_COMPLETE | El moderador ha terminado de representar todos los fotogramas después del mensaje MFVP_MESSAGE_ENDOFSTREAM.
|
EC_DISPLAY_CHANGED | El dispositivo de Direct3D ha cambiado.
|
EC_ERRORABORT | Se ha producido un error que requiere que se detenga el streaming.
|
EC_PROCESSING_LATENCY | Especifica la cantidad de tiempo que tarda el moderador en representar cada fotograma. (Opcional).
|
EC_SAMPLE_LATENCY | Especifica el tiempo de retraso actual en los ejemplos de representación. Si el valor es positivo, las muestras están retrasadas. Si el valor es negativo, las muestras están por delante de la programación. (Opcional).
|
EC_SCRUB_TIME | Se envía inmediatamente después de EC_STEP_COMPLETE si la velocidad de reproducción es cero. Este evento contiene la marca de tiempo del fotograma que se mostró.
|
EC_STEP_COMPLETE | El moderador ha completado o cancelado un paso de fotograma. - Param1: no se usa. - Param2: No se usa. Para obtener más información, consulte Avance en los fotogramas. Nota: Una versión anterior de la documentación describía Param1 incorrectamente. Este parámetro no se usa para este evento. |
Negociación de formatos
Cada vez que el moderador recibe un mensaje MFVP_MESSAGE_INVALIDATEMEDIATYPE del EVR, debe establecer el formato de salida en el mezclador, como se indica a continuación:
Llame a IMFTransform::GetOutputAvailableType en el mezclador para obtener un posible tipo de salida. Este tipo describe un formato que el mezclador puede producir dados los flujos de entrada y las capacidades de procesamiento de vídeo del dispositivo gráfico.
Compruebe si el moderador puede usar este tipo de elemento multimedia como formato de representación. Estos son algunos aspectos que se deben comprobar, aunque la implementación puede tener sus propios requisitos:
- El vídeo no debe estar comprimido.
- El vídeo solo debe tener fotogramas progresivos. Compruebe que el atributo MF_MT_INTERLACE_MODE sea igual a MFVideoInterlace_Progressive.
- El formato debe ser compatible con el dispositivo de Direct3D.
Si el tipo no es aceptable, vuelva al paso 1 y obtenga el siguiente tipo propuesto del mezclador.
Cree un nuevo tipo de elemento multimedia que sea un clon del tipo original y, a continuación, cambie los atributos siguientes:
- Establezca el atributo MF_MT_FRAME_SIZE igual al ancho y alto que desee para las superficies de Direct3D que asignará.
- Establezca el atributo MF_MT_PAN_SCAN_ENABLED en FALSE.
- Establezca el atributo MF_MT_PIXEL_ASPECT_RATIO igual a la PAR de la pantalla (normalmente 1:1).
- Establezca la apertura geométrica (atributo MF_MT_GEOMETRIC_APERTURE) igual a un rectángulo dentro de la superficie de Direct3D. Cuando el mezclador genera un fotograma de salida, transfiere los bloques de bits de la imagen de origen en este rectángulo. La abertura geométrica puede ser tan grande como la superficie o puede ser un subrectángulo dentro de la superficie. Para obtener más información, consulte Rectángulos de origen y destino.
Para comprobar si el mezclador aceptará el tipo de salida modificado, llame a IMFTransform::SetOutputType con la marca MFT_SET_TYPE_TEST_ONLY. Si el mezclador rechaza el tipo, vuelva al paso 1 y obtenga el siguiente tipo.
Asigne un grupo de superficies de Direct3D, como se describe en Asignar superficies de Direct3D. El mezclador usará estas superficies cuando dibuje los fotogramas de vídeo compuestos.
Establezca el tipo de salida en el mezclador llamando a SetOutputType sin marcas. Si la primera llamada a SetOutputType se realizó correctamente en el paso 4, el método debería volver a realizarse correctamente.
Si el mezclador se queda sin tipos, el método GetOutputAvailableType devuelve MF_E_NO_MORE_TYPES. Si el moderador no encuentra un tipo de salida adecuado para el mezclador, no se puede representar la secuencia. En ese caso, DirectShow o Media Foundation podrían probar otro formato de secuencia. Por lo tanto, el moderador puede recibir varios mensajes MFVP_MESSAGE_INVALIDATEMEDIATYPE en una fila hasta que se encuentre un tipo válido.
El mezclador automáticamente aplica el formato letterbox al vídeo, teniendo en cuenta la relación de aspecto de píxeles (PAR) del origen y el destino. Para obtener los mejores resultados, el ancho y la altura de la superficie y la abertura geométrica deben ser iguales al tamaño real en el que desea que el vídeo aparezca en la pantalla. En la imagen siguiente se muestra este proceso.
El código siguiente muestra el esquema del proceso. Algunos de los pasos se colocan en funciones auxiliares; los detalles exactos dependerán de los requisitos del moderador.
HRESULT EVRCustomPresenter::RenegotiateMediaType()
{
HRESULT hr = S_OK;
BOOL bFoundMediaType = FALSE;
IMFMediaType *pMixerType = NULL;
IMFMediaType *pOptimalType = NULL;
IMFVideoMediaType *pVideoType = NULL;
if (!m_pMixer)
{
return MF_E_INVALIDREQUEST;
}
// Loop through all of the mixer's proposed output types.
DWORD iTypeIndex = 0;
while (!bFoundMediaType && (hr != MF_E_NO_MORE_TYPES))
{
SafeRelease(&pMixerType);
SafeRelease(&pOptimalType);
// Step 1. Get the next media type supported by mixer.
hr = m_pMixer->GetOutputAvailableType(0, iTypeIndex++, &pMixerType);
if (FAILED(hr))
{
break;
}
// From now on, if anything in this loop fails, try the next type,
// until we succeed or the mixer runs out of types.
// Step 2. Check if we support this media type.
if (SUCCEEDED(hr))
{
// Note: None of the modifications that we make later in CreateOptimalVideoType
// will affect the suitability of the type, at least for us. (Possibly for the mixer.)
hr = IsMediaTypeSupported(pMixerType);
}
// Step 3. Adjust the mixer's type to match our requirements.
if (SUCCEEDED(hr))
{
hr = CreateOptimalVideoType(pMixerType, &pOptimalType);
}
// Step 4. Check if the mixer will accept this media type.
if (SUCCEEDED(hr))
{
hr = m_pMixer->SetOutputType(0, pOptimalType, MFT_SET_TYPE_TEST_ONLY);
}
// Step 5. Try to set the media type on ourselves.
if (SUCCEEDED(hr))
{
hr = SetMediaType(pOptimalType);
}
// Step 6. Set output media type on mixer.
if (SUCCEEDED(hr))
{
hr = m_pMixer->SetOutputType(0, pOptimalType, 0);
assert(SUCCEEDED(hr)); // This should succeed unless the MFT lied in the previous call.
// If something went wrong, clear the media type.
if (FAILED(hr))
{
SetMediaType(NULL);
}
}
if (SUCCEEDED(hr))
{
bFoundMediaType = TRUE;
}
}
SafeRelease(&pMixerType);
SafeRelease(&pOptimalType);
SafeRelease(&pVideoType);
return hr;
}
Para obtener más información sobre los tipos de elementos multimedia de vídeo, consulte Tipos de elementos multimedia de vídeo.
Administración del dispositivo de Direct3D
El moderador crea el dispositivo de Direct3D y controla cualquier pérdida de dispositivo durante el streaming. El moderador también hospeda el administrador de dispositivos de Direct3D, que proporciona una manera de que otros componentes usen el mismo dispositivo. Por ejemplo, el mezclador usa el dispositivo de Direct3D para mezclar subsecuencias, deshacer entrelazado y realizar ajustes de color. Los descodificadores pueden usar el dispositivo de Direct3D para la descodificación acelerada por vídeo. (Para obtener más información sobre la aceleración de vídeo, consulte Aceleración de vídeo de DirectX 2.0).
Para configurar el dispositivo de Direct3D, realice los pasos siguientes:
- Cree el objeto de Direct3D llamando a Direct3DCreate9 o Direct3DCreate9Ex.
- Cree el dispositivo llamando a IDirect3D9::CreateDevice o IDirect3D9Ex::CreateDevice.
- Cree el administrador de dispositivos llamando a DXVA2CreateDirect3DDeviceManager9.
- Establezca el dispositivo en el administrador de dispositivos llamando a IDirect3DDeviceManager9::ResetDevice.
Si otro componente de canalización necesita al administrador de dispositivos, llama a IMFGetService::GetService en el EVR, especificando MR_VIDEO_ACCELERATION_SERVICE para el GUID de servicio. El EVR pasa la solicitud al moderador. Una vez que el objeto obtiene el puntero IDirect3DDeviceManager9, puede obtener un controlador para el dispositivo llamando a IDirect3DDeviceManager9::OpenDeviceHandle. Cuando el objeto necesita usar el dispositivo, pasa el controlador de dispositivo al método IDirect3DDeviceManager9::LockDevice, que devuelve un puntero IDirect3DDevice9.
Una vez creado el dispositivo, si el moderador destruye el dispositivo y crea uno nuevo, el moderador debe llamar a ResetDevice de nuevo. El método ResetDevice invalida los controladores de dispositivo existentes, lo que hace que LockDevice devuelva DXVA2_E_NEW_VIDEO_DEVICE. Este código de error indica a otros objetos que usan el dispositivo que deben abrir un nuevo controlador de dispositivo. Para obtener más información sobre el uso del administrador de dispositivos, consulte Administrador de dispositivos de Direct3D.
El moderador puede crear el dispositivo en modo de ventana o en modo exclusivo de pantalla completa. Para el modo de ventana, debe proporcionar una manera de que la aplicación especifique la ventana de vídeo. El moderador estándar implementa el método IMFVideoDisplayControl::SetVideoWindow para este propósito. Debe crear el dispositivo cuando se cree por primera vez el moderador. Normalmente, no conocerá todos los parámetros del dispositivo en este momento, como la ventana o el formato de búfer de reserva. Puede crear un dispositivo temporal y reemplazarlo más adelante&; solo recuerde llamar a ResetDevice en el administrador de dispositivos.
Si crea un nuevo dispositivo o llama a IDirect3DDevice9::Reset o IDirect3DDevice9Ex::ResetEx en un dispositivo existente, envíe un evento EC_DISPLAY_CHANGED al EVR. Este evento notifica al EVR que vuelva a negociar el tipo de elemento multimedia. El EVR omite los parámetros de evento de este evento.
Asignación de superficies de Direct3D
Una vez que el moderador establece el tipo de elemento multimedia, puede asignar las superficies de Direct3D, que el mezclador usará para escribir los fotogramas de vídeo. La superficie debe coincidir con el tipo de elemento multimedia del moderador:
- El formato de superficie debe coincidir con el subtipo de elemento multimedia. Por ejemplo, si el subtipo es MFVideoFormat_RGB24, el formato de superficie debe ser D3DFMT_X8R8G8B8. Para obtener más información sobre los subtipos y los formatos de Direct3D, consulte GUID de subtipo de vídeo.
- El ancho y el alto de la superficie deben coincidir con las dimensiones dadas en el atributo MF_MT_FRAME_SIZE del tipo de elemento multimedia.
La manera recomendada de asignar superficies depende de si el moderador ejecuta el modo de ventana o el modo de pantalla completa.
Si el dispositivo de Direct3D está en modo de ventana, puede crear varias cadenas de intercambio, cada una con un solo búfer de reserva. Con este enfoque, puede presentar cada superficie de forma independiente, ya que la presentación de una cadena de intercambio no interferirá con las demás cadenas de intercambio. El mezclador puede escribir datos en una superficie mientras otra superficie está programada para su presentación.
En primer lugar, decida cuántas cadenas de intercambio se van a crear. Se recomienda un mínimo de tres. Para cada cadena de intercambio, haga lo siguiente:
- Llame a IDirect3DDevice9::CreateAdditionalSwapChain para crear la cadena de intercambio.
- Llame a IDirect3DSwapChain9::GetBackBuffer para obtener un puntero a la superficie de búfer de reserva de la cadena de intercambio.
- Llame a MFCreateVideoSampleFromSurface y pase un puntero a la superficie. Esta función devuelve un puntero a un objeto de muestra de vídeo. El objeto de muestra de vídeo implementa la interfaz IMFSample y el moderador usa esta interfaz para entregar la superficie al mezclador cuando el moderador llama al método IMFTransform::ProcessOutput del mezclador. Para obtener más información sobre el objeto de muestra de vídeo, consulte Muestras de vídeo.
- Almacene el puntero IMFSample en una cola. El moderador extraerá muestras de esta cola durante el procesamiento, como se describe en Procesamiento de la salida.
- Mantenga una referencia al puntero IDirect3DSwapChain9 para que la cadena de intercambio no se libere.
En modo exclusivo de pantalla completa, el dispositivo no puede tener más de una cadena de intercambio. Esta cadena de intercambio se crea implícitamente al crear el dispositivo de pantalla completa. La cadena de intercambio puede tener más de un búfer de reserva. Sin embargo, desafortunadamente, si presenta un búfer de reserva mientras escribe en otro búfer de reserva en la misma cadena de intercambio, no hay ninguna manera fácil de coordinar las dos operaciones. Esto se debe a la forma en que Direct3D implementa el volteo de superficie. Al llamar a Present, el controlador de gráficos actualiza los punteros de superficie en la memoria de gráficos. Si está manteniendo punteros IDirect3DSurface9 al llamar a Present, apuntarán a diferentes búferes después de que se devuelva la llamada Present.
La opción más sencilla es crear una muestra de vídeo para la cadena de intercambio. Si elige esta opción, siga los mismos pasos indicados para el modo de ventana. La única diferencia es que la cola de muestra contiene una sola muestra de vídeo. Otra opción es crear superficies fuera de pantalla y, a continuación, realizar una transferencia de bloques de bits al búfer de reserva. Las superficies que cree deben admitir el método IDirectXVideoProcessor::VideoProcessBlt, que el mezclador usa para componer los fotogramas de salida.
Seguimiento de muestras
Cuando el moderador asigna primero las muestras de vídeo, las coloca en una cola. El moderador extrae de esta cola siempre que necesite obtener un nuevo fotograma del mezclador. Una vez que el mezclador produce el fotograma, el moderador mueve la muestra a una segunda cola. La segunda cola es para muestras que están esperando sus tiempos de presentación programados.
Para facilitar el seguimiento del estado de cada muestra, el objeto de muestra de vídeo implementa la interfaz IMFTrackedSample. Puede usar esta interfaz de la siguiente manera:
Implemente la interfaz IMFAsyncCallback en el moderador.
Antes de colocar una muestra en la cola programada, consulte el objeto de muestra de vídeo para la interfaz IMFTrackedSample.
Llame a IMFTrackedSample::SetAllocator con un puntero a la interfaz de devolución de llamada.
Cuando la muestra esté lista para su presentación, quítela de la cola programada, preséntela y libere todas las referencias a la muestra.
La muestra invoca la devolución de llamada. (El objeto de muestra no se elimina en este caso porque contiene un recuento de referencias en sí mismo hasta que se invoca la devolución de llamada).
Dentro de la devolución de llamada, devuelva la muestra a la cola disponible.
No es necesario que un moderador use IMFTrackedSample para realizar un seguimiento de las muestras; puede implementar cualquier técnica que funcione mejor para su diseño. Una ventaja de IMFTrackedSample es que puede mover las funciones de programación y representación del moderador a objetos auxiliares, y estos objetos no necesitan ningún mecanismo especial para volver a llamar al moderador cuando liberan muestras de vídeo porque el objeto de muestra proporciona ese mecanismo.
El código siguiente muestra cómo establecer la devolución de llamada:
HRESULT EVRCustomPresenter::TrackSample(IMFSample *pSample)
{
IMFTrackedSample *pTracked = NULL;
HRESULT hr = pSample->QueryInterface(IID_PPV_ARGS(&pTracked));
if (SUCCEEDED(hr))
{
hr = pTracked->SetAllocator(&m_SampleFreeCB, NULL);
}
SafeRelease(&pTracked);
return hr;
}
En la devolución de llamada, llame a IMFAsyncResult::GetObject en el objeto de resultado asincrónico para recuperar un puntero a la muestra:
HRESULT EVRCustomPresenter::OnSampleFree(IMFAsyncResult *pResult)
{
IUnknown *pObject = NULL;
IMFSample *pSample = NULL;
IUnknown *pUnk = NULL;
// Get the sample from the async result object.
HRESULT hr = pResult->GetObject(&pObject);
if (FAILED(hr))
{
goto done;
}
hr = pObject->QueryInterface(IID_PPV_ARGS(&pSample));
if (FAILED(hr))
{
goto done;
}
// If this sample was submitted for a frame-step, the frame step operation
// is complete.
if (m_FrameStep.state == FRAMESTEP_SCHEDULED)
{
// Query the sample for IUnknown and compare it to our cached value.
hr = pSample->QueryInterface(IID_PPV_ARGS(&pUnk));
if (FAILED(hr))
{
goto done;
}
if (m_FrameStep.pSampleNoRef == (DWORD_PTR)pUnk)
{
// Notify the EVR.
hr = CompleteFrameStep(pSample);
if (FAILED(hr))
{
goto done;
}
}
// Note: Although pObject is also an IUnknown pointer, it is not
// guaranteed to be the exact pointer value returned through
// QueryInterface. Therefore, the second QueryInterface call is
// required.
}
/*** Begin lock ***/
EnterCriticalSection(&m_ObjectLock);
UINT32 token = MFGetAttributeUINT32(
pSample, MFSamplePresenter_SampleCounter, (UINT32)-1);
if (token == m_TokenCounter)
{
// Return the sample to the sample pool.
hr = m_SamplePool.ReturnSample(pSample);
if (SUCCEEDED(hr))
{
// A free sample is available. Process more data if possible.
(void)ProcessOutputLoop();
}
}
LeaveCriticalSection(&m_ObjectLock);
/*** End lock ***/
done:
if (FAILED(hr))
{
NotifyEvent(EC_ERRORABORT, hr, 0);
}
SafeRelease(&pObject);
SafeRelease(&pSample);
SafeRelease(&pUnk);
return hr;
}
Procesamiento de la salida
Cada vez que el mezclador recibe una nueva muestra de entrada, el EVR envía un mensaje MFVP_MESSAGE_PROCESSINPUTNOTIFY al moderador. Este mensaje indica que el mezclador podría tener un nuevo fotograma de vídeo que se va a entregar. En respuesta, el moderador llama a IMFTransform::ProcessOutput en el mezclador. Si el método se realiza correctamente, el moderador programa la muestra para la presentación.
Para obtener la salida del mezclador, realice los pasos siguientes:
Compruebe el estado del reloj. Si el reloj está en pausa, omita el mensaje MFVP_MESSAGE_PROCESSINPUTNOTIFY a menos que este sea el primer fotograma de vídeo. Si el reloj se está ejecutando o si se trata del primer fotograma de vídeo, continúe.
Obtenga una muestra de la cola de muestras disponibles. Si la cola está vacía, significa que todas las muestras asignadas están programadas actualmente para presentarse. En ese caso, omita el mensaje MFVP_MESSAGE_PROCESSINPUTNOTIFY en este momento. Cuando la muestra siguiente esté disponible, repita los pasos que se enumeran aquí.
(Opcional). Si el reloj está disponible, obtenga la hora del reloj actual (T1) llamando a IMFClock::GetCorrelatedTime.
Llame a IMFTransform::ProcessOutput en el mezclador. Si ProcessOutput se realiza correctamente, la muestra contiene un fotograma de vídeo. Si se produce un error en el método, compruebe el código devuelto. Los siguientes códigos de error de ProcessOutput no son errores críticos:
Código de error Descripción MF_E_TRANSFORM_NEED_MORE_INPUT El mezclador necesita más entrada para poder producir un nuevo fotograma de salida.
Si recibe este código de error, compruebe si el EVR ha llegado al final de la secuencia y responda en consecuencia, tal como se describe en Fin de la secuencia. De lo contrario, omita este mensaje MF_E_TRANSFORM_NEED_MORE_INPUT. El EVR enviará otro cuando el mezclador obtenga más entrada.MF_E_TRANSFORM_STREAM_CHANGE El tipo de salida del mezclador ya no es válido, posiblemente debido a un cambio de formato en el canal de subida.
Si obtiene este código de error, establezca el tipo de elemento multimedia del moderador en NULL. El EVR solicitará un nuevo formato.MF_E_TRANSFORM_TYPE_NOT_SET El mezclador requiere un nuevo tipo de elemento multimedia.
Si obtiene este código de error, vuelva a negociar el tipo de salida del mezclador como se describe en Negociación de formatos.Si ProcessOutput se realiza correctamente, continúe.
(Opcional). Si el reloj está disponible, obtenga la hora del reloj actual (T2). La cantidad de latencia introducida por el mezclador es (T2 - T1). Envíe un evento EC_PROCESSING_LATENCY con este valor al EVR. El EVR usa este valor para el control de calidad. Si no hay ningún reloj disponible, no hay ninguna razón para enviar el evento EC_PROCESSING_LATENCY.
(Opcional). Consulte la muestra de IMFTrackedSample y llame a IMFTrackedSample::SetAllocator como se describe en Seguimiento de muestras.
Programe la muestra para la presentación.
Esta secuencia de pasos puede finalizar antes de que el moderador obtenga una salida del mezclador. Para asegurarse de que no se quita ninguna solicitud, debe repetir los mismos pasos cuando se produzca lo siguiente:
- Se llama al método IMFClockStateSink::OnClockStart o IMFClockStateSink::OnClockStart del moderador. Esto controla el caso en el que el mezclador omite la entrada porque el reloj está en pausa (paso 1).
- Se invoca la devolución de llamada IMFTrackedSample. Esto controla el caso en el que el mezclador recibe la entrada, pero todas las muestras de vídeo del moderador están en uso (paso 2).
En los siguientes ejemplos de código se muestran estos pasos con más detalle. El moderador llama al método ProcessInputNotify
(que se muestra en el ejemplo siguiente) cuando obtiene el mensaje MFVP_MESSAGE_PROCESSINPUTNOTIFY.
//-----------------------------------------------------------------------------
// ProcessInputNotify
//
// Attempts to get a new output sample from the mixer.
//
// This method is called when the EVR sends an MFVP_MESSAGE_PROCESSINPUTNOTIFY
// message, which indicates that the mixer has a new input sample.
//
// Note: If there are multiple input streams, the mixer might not deliver an
// output sample for every input sample.
//-----------------------------------------------------------------------------
HRESULT EVRCustomPresenter::ProcessInputNotify()
{
HRESULT hr = S_OK;
// Set the flag that says the mixer has a new sample.
m_bSampleNotify = TRUE;
if (m_pMediaType == NULL)
{
// We don't have a valid media type yet.
hr = MF_E_TRANSFORM_TYPE_NOT_SET;
}
else
{
// Try to process an output sample.
ProcessOutputLoop();
}
return hr;
}
Este método ProcessInputNotify
establece una marca booleana para registrar el hecho de que el mezclador tiene una entrada nueva. A continuación, llama al método ProcessOutputLoop
, que se muestra en el ejemplo siguiente. Este método intenta extraer tantas muestras como sea posible del mezclador:
void EVRCustomPresenter::ProcessOutputLoop()
{
HRESULT hr = S_OK;
// Process as many samples as possible.
while (hr == S_OK)
{
// If the mixer doesn't have a new input sample, break from the loop.
if (!m_bSampleNotify)
{
hr = MF_E_TRANSFORM_NEED_MORE_INPUT;
break;
}
// Try to process a sample.
hr = ProcessOutput();
// NOTE: ProcessOutput can return S_FALSE to indicate it did not
// process a sample. If so, break out of the loop.
}
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
{
// The mixer has run out of input data. Check for end-of-stream.
CheckEndOfStream();
}
}
El método ProcessOutput
, que se muestra en el ejemplo siguiente, intenta obtener un solo fotograma de vídeo del mezclador. Si no hay ningún fotograma de vídeo disponible, ProcessSample
devuelve S_FALSE o un código de error, cualquiera de los cuales interrumpe el bucle en ProcessOutputLoop
. La mayoría del trabajo se realiza dentro del método ProcessOutput
:
//-----------------------------------------------------------------------------
// ProcessOutput
//
// Attempts to get a new output sample from the mixer.
//
// Called in two situations:
// (1) ProcessOutputLoop, if the mixer has a new input sample.
// (2) Repainting the last frame.
//-----------------------------------------------------------------------------
HRESULT EVRCustomPresenter::ProcessOutput()
{
assert(m_bSampleNotify || m_bRepaint); // See note above.
HRESULT hr = S_OK;
DWORD dwStatus = 0;
LONGLONG mixerStartTime = 0, mixerEndTime = 0;
MFTIME systemTime = 0;
BOOL bRepaint = m_bRepaint; // Temporarily store this state flag.
MFT_OUTPUT_DATA_BUFFER dataBuffer;
ZeroMemory(&dataBuffer, sizeof(dataBuffer));
IMFSample *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_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 == NULL)
{
return MF_E_INVALIDREQUEST;
}
// Try to get a free sample from the video sample pool.
hr = m_SamplePool.GetSample(&pSample);
if (hr == MF_E_SAMPLEALLOCATOR_EMPTY)
{
// No free samples. Try again when a sample is released.
return S_FALSE;
}
else if (FAILED(hr))
{
return hr;
}
// 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.)
assert(MFGetAttributeUINT32(pSample, MFSamplePresenter_SampleCounter, (UINT32)-1) == m_TokenCounter);
if (m_bRepaint)
{
// Repaint request. Ask the mixer for the most recent sample.
SetDesiredSampleTime(
pSample,
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)
{
// Latency: Record the starting time for ProcessOutput.
(void)m_pClock->GetCorrelatedTime(0, &mixerStartTime, &systemTime);
}
}
// Now we are ready to get an output sample from the mixer.
dataBuffer.dwStreamID = 0;
dataBuffer.pSample = pSample;
dataBuffer.dwStatus = 0;
hr = m_pMixer->ProcessOutput(0, 1, &dataBuffer, &dwStatus);
if (FAILED(hr))
{
// Return the sample to the pool.
HRESULT hr2 = m_SamplePool.ReturnSample(pSample);
if (FAILED(hr2))
{
hr = hr2;
goto done;
}
// Handle some known error codes from ProcessOutput.
if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
{
// The mixer's format is not set. Negotiate a new format.
hr = RenegotiateMediaType();
}
else if (hr == MF_E_TRANSFORM_STREAM_CHANGE)
{
// There was a dynamic media type change. Clear our media type.
SetMediaType(NULL);
}
else if (hr == 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 && !bRepaint)
{
// Latency: Record the ending time for the ProcessOutput operation,
// and notify the EVR of the latency.
(void)m_pClock->GetCorrelatedTime(0, &mixerEndTime, &systemTime);
LONGLONG latencyTime = mixerEndTime - mixerStartTime;
NotifyEvent(EC_PROCESSING_LATENCY, (LONG_PTR)&latencyTime, 0);
}
// Set up notification for when the sample is released.
hr = TrackSample(pSample);
if (FAILED(hr))
{
goto done;
}
// Schedule the sample.
if ((m_FrameStep.state == FRAMESTEP_NONE) || bRepaint)
{
hr = DeliverSample(pSample, bRepaint);
if (FAILED(hr))
{
goto done;
}
}
else
{
// We are frame-stepping (and this is not a repaint request).
hr = DeliverFrameStepSample(pSample);
if (FAILED(hr))
{
goto done;
}
}
m_bPrerolled = TRUE; // We have presented at least one sample now.
}
done:
SafeRelease(&pSample);
// Important: Release any events returned from the ProcessOutput method.
SafeRelease(&dataBuffer.pEvents);
return hr;
}
Algunos comentarios sobre este ejemplo:
- Se supone que la variable m_SamplePool es un objeto de colección que contiene la cola de muestras de vídeo disponibles. El método
GetSample
del objeto devuelve MF_E_SAMPLEALLOCATOR_EMPTY si la cola está vacía. - Si el método ProcessOutput del mezclador devuelve MF_E_TRANSFORM_NEED_MORE_INPUT, significa que el mezclador no puede producir más salida, por lo que el moderador borra la marca m_fSampleNotify.
- El método
TrackSample
, que establece la devolución de llamada IMFTrackedSample, se muestra en la sección Seguimiento de muestras.
Volver a dibujar fotogramas
En ocasiones, es posible que el moderador tenga que volver a dibujar el fotograma de vídeo más reciente. Por ejemplo, el moderador estándar vuelve a dibujar el fotograma en las situaciones siguientes:
- En modo de ventana, en respuesta a mensajes WM_PAINT recibidos por la ventana de la aplicación. Consulte IMFVideoDisplayControl::RepaintVideo.
- Si el rectángulo de destino cambia cuando la aplicación llama a IMFVideoDisplayControl::SetVideoPosition.
Siga estos pasos para solicitar al mezclador que vuelva a crear el fotograma más reciente:
- Obtenga una muestra de vídeo de la cola.
- Consulte la muestra de la interfaz IMFDesiredSample.
- Llame a IMFDesiredSample::SetDesiredSampleTimeAndDuration. Especifique la marca de tiempo del fotograma de vídeo más reciente. (Tendrá que almacenar en caché este valor y actualizarlo para cada fotograma).
- Llame a ProcessOutput en el mezclador.
Al volver a dibujar un fotograma, puede omitir el reloj de presentación y presentar el fotograma inmediatamente.
Programación de muestras
Los fotogramas de vídeo pueden llegar al EVR en cualquier momento. El moderador es responsable de presentar cada fotograma en el momento correcto, en función de la marca de tiempo del fotograma. Cuando el moderador obtiene una nueva muestra del mezclador, coloca la muestra en la cola programada. En un subproceso independiente, el moderador obtiene continuamente la primera muestra del encabezado de la cola y determina si:
- Presenta la muestra.
- Mantiene la muestra en la cola porque es temprano.
- Descarta la muestra porque es tarde. Aunque debe evitar quitar fotogramas si es posible, podría tener que hacerlo si el moderador está continuamente atrasado.
Para obtener la marca de tiempo de un fotograma de vídeo, llame a IMFSample::GetSampleTime en la muestra de vídeo. La marca de tiempo es relativa al reloj de presentación del EVR. Para obtener la hora del reloj actual, llame a IMFClock::GetCorrelatedTime. Si el EVR no tiene un reloj de presentación o si una muestra no tiene una marca de tiempo, puede presentar la muestra inmediatamente después de obtenerla.
Para obtener la duración de cada muestra, llame a IMFSample::GetSampleDuration. Si la muestra no tiene una duración, puede usar la función MFFrameRateToAverageTimePerFrame para calcular la duración a partir de la velocidad de fotogramas.
Al programar muestras, tenga en cuenta lo siguiente:
- Si la velocidad de reproducción es más rápida o más lenta que la velocidad normal, el reloj se ejecuta a una velocidad más rápida o más lenta. Esto significa que la marca de tiempo de una muestra siempre proporciona la hora de destino correcta en relación con el reloj de presentación. Sin embargo, si traduce los tiempos de presentación en algún otro tiempo de reloj (por ejemplo, el contador de rendimiento de alta resolución), debe escalar las horas en función de la velocidad del reloj. Si cambia la velocidad del reloj, el EVR llama al método IMFClockStateSink::OnClockSetRate del moderador.
- La velocidad de reproducción puede ser negativa para la reproducción inversa. Cuando la velocidad de reproducción es negativa, el reloj de presentación se ejecuta hacia atrás. En otras palabras, la hora N + 1 se produce antes de la hora N.
En el ejemplo siguiente se calcula qué tan temprano o qué tan tarde está una muestra, en relación con el reloj de presentación:
LONGLONG hnsPresentationTime = 0;
LONGLONG hnsTimeNow = 0;
MFTIME hnsSystemTime = 0;
BOOL bPresentNow = TRUE;
LONG lNextSleep = 0;
if (m_pClock)
{
// Get the sample's time stamp. It is valid for a sample to
// have no time stamp.
hr = pSample->GetSampleTime(&hnsPresentationTime);
// Get the clock time. (But if the sample does not have a time stamp,
// we don't need the clock time.)
if (SUCCEEDED(hr))
{
hr = m_pClock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime);
}
// Calculate the time until the sample's presentation time.
// A negative value means the sample is late.
LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow;
if (m_fRate < 0)
{
// For reverse playback, the clock runs backward. Therefore, the
// delta is reversed.
hnsDelta = - hnsDelta;
}
El reloj de presentación suele estar controlado por el reloj del sistema o el representador de audio. (El representador de audio deriva el tiempo de la velocidad a la que la tarjeta de sonido consume audio). En general, el reloj de presentación no se sincroniza con la frecuencia de actualización del monitor.
Si los parámetros de presentación de Direct3D especifican D3DPRESENT_INTERVAL_DEFAULT o D3DPRESENT_INTERVAL_ONE para el intervalo de presentación, la operación Present espera la supresión vertical del monitor. Esta es una manera fácil de evitar el rasgado, pero reduce la precisión del algoritmo de programación. Por el contrario, si el intervalo de presentación es D3DPRESENT_INTERVAL_IMMEDIATE, el método Present se ejecuta inmediatamente, lo que provoca el rasgado a menos que el algoritmo de programación sea lo suficientemente preciso como para llamar a Present solo durante el período de supresión vertical.
Las siguientes funciones pueden ayudarle a obtener información de tiempo precisa:
- IDirect3DDevice9::GetRasterStatus devuelve información sobre la imagen ráster, incluida la línea de examen actual y si la imagen ráster está en el período de supresión vertical.
- DwmGetCompositionTimingInfo devuelve información de tiempo para el administrador de ventanas de escritorio. Esta información es útil si la composición del escritorio está habilitada.
Presentación de muestras
En esta sección se supone que ha creado una cadena de intercambio independiente para cada superficie, tal como se describe en Asignar superficies de Direct3D. Para presentar una muestra, obtenga la cadena de intercambio de la muestra de vídeo de la siguiente manera:
- Llame a IMFSample::GetBufferByIndex en la muestra de vídeo para obtener el búfer.
- Consulte el búfer de la interfaz IMFGetService.
- Llame a IMFGetService::GetService para obtener la interfaz IDirect3DSurface9 de la superficie de Direct3D. (Puede combinar este paso y el paso anterior en uno mediante una llamada a MFGetService).
- Llame a IDirect3DSurface9::GetContainer en la superficie para obtener un puntero a la cadena de intercambio.
- Llame a IDirect3DSwapChain9::Present en la cadena de intercambio.
El siguiente código muestra estos pasos:
HRESULT D3DPresentEngine::PresentSample(IMFSample* pSample, LONGLONG llTarget)
{
HRESULT hr = S_OK;
IMFMediaBuffer* pBuffer = NULL;
IDirect3DSurface9* pSurface = NULL;
IDirect3DSwapChain9* pSwapChain = NULL;
if (pSample)
{
// Get the buffer from the sample.
hr = pSample->GetBufferByIndex(0, &pBuffer);
if (FAILED(hr))
{
goto done;
}
// Get the surface from the buffer.
hr = MFGetService(pBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pSurface));
if (FAILED(hr))
{
goto done;
}
}
else if (m_pSurfaceRepaint)
{
// Redraw from the last surface.
pSurface = m_pSurfaceRepaint;
pSurface->AddRef();
}
if (pSurface)
{
// Get the swap chain from the surface.
hr = pSurface->GetContainer(IID_PPV_ARGS(&pSwapChain));
if (FAILED(hr))
{
goto done;
}
// Present the swap chain.
hr = PresentSwapChain(pSwapChain, pSurface);
if (FAILED(hr))
{
goto done;
}
// Store this pointer in case we need to repaint the surface.
CopyComPointer(m_pSurfaceRepaint, pSurface);
}
else
{
// No surface. All we can do is paint a black rectangle.
PaintFrameWithGDI();
}
done:
SafeRelease(&pSwapChain);
SafeRelease(&pSurface);
SafeRelease(&pBuffer);
if (FAILED(hr))
{
if (hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET || hr == D3DERR_DEVICEHUNG)
{
// We failed because the device was lost. Fill the destination rectangle.
PaintFrameWithGDI();
// Ignore. We need to reset or re-create the device, but this method
// is probably being called from the scheduler thread, which is not the
// same thread that created the device. The Reset(Ex) method must be
// called from the thread that created the device.
// The presenter will detect the state when it calls CheckDeviceState()
// on the next sample.
hr = S_OK;
}
}
return hr;
}
Rectángulos de origen y destino
El rectángulo de origen es la parte del fotograma de vídeo que se va a mostrar. Se define en relación con un sistema de coordenadas normalizado, en el que todo el fotograma de vídeo ocupa un rectángulo con coordenadas {0, 0, 1, 1}. El rectángulo de destino es el área dentro de la superficie de destino donde se dibuja el fotograma de vídeo. El moderador estándar permite que una aplicación establezca estos rectángulos llamando a IMFVideoDisplayControl::SetVideoPosition.
Hay varias opciones para aplicar rectángulos de origen y destino. La primera opción es permitir que el mezclador los aplique:
- Establezca el rectángulo de origen mediante el atributo VIDEO_ZOOM_RECT. El mezclador aplicará el rectángulo de origen cuando realice la transferencia de bloques de bits del vídeo en la superficie de destino. El rectángulo de origen predeterminado del mezclador es todo el fotograma.
- Establezca el rectángulo de destino como la apertura geométrica en el tipo de salida del mezclador. Para obtener más información, consulte Negociación de formatos.
La segunda opción es aplicar los rectángulos cuando IDirect3DSwapChain9::Present especificando los parámetros pSourceRect y pDestRect en el método Present. Puede combinar estas opciones. Por ejemplo, podría establecer el rectángulo de origen en el mezclador, pero aplicar el rectángulo de destino en el método Present.
Si la aplicación cambia el rectángulo de destino o cambia el tamaño de la ventana, es posible que tenga que asignar nuevas superficies. Si es así, debe tener cuidado de sincronizar esta operación con el subproceso de programación. Vacíe la cola de programación y descarte las muestras antiguas antes de asignar nuevas superficies.
Fin de la secuencia
Cuando finaliza cada flujo de entrada del EVR, el EVR envía un mensaje MFVP_MESSAGE_ENDOFSTREAM al moderador. Sin embargo, en el momento en que reciba el mensaje, puede que haya algunos fotogramas de vídeo para procesar. Antes de responder al mensaje de final de la secuencia, debe purgar toda la salida del mezclador y presentar todos los fotogramas restantes. Después de presentar el último fotograma, envíe un evento EC_COMPLETE al EVR.
En el ejemplo siguiente se muestra un método que envía el evento EC_COMPLETE si se cumplen varias condiciones. De lo contrario, devuelve S_OK sin enviar el evento:
HRESULT EVRCustomPresenter::CheckEndOfStream()
{
if (!m_bEndStreaming)
{
// The EVR did not send the MFVP_MESSAGE_ENDOFSTREAM message.
return S_OK;
}
if (m_bSampleNotify)
{
// The mixer still has input.
return S_OK;
}
if (m_SamplePool.AreSamplesPending())
{
// Samples are still scheduled for rendering.
return S_OK;
}
// Everything is complete. Now we can tell the EVR that we are done.
NotifyEvent(EC_COMPLETE, (LONG_PTR)S_OK, 0);
m_bEndStreaming = FALSE;
return S_OK;
}
Este método comprueba los siguientes estados:
- Si la variable m_fSampleNotify es TRUE, significa que el mezclador tiene uno o varios fotogramas que aún no se han procesado. (Para obtener más información, consulte Procesamiento de la salida).
- La variable m_fEndStreaming es una marca booleana cuyo valor inicial es FALSE. El moderador establece la marca en TRUE cuando el EVR envía el mensaje MFVP_MESSAGE_ENDOFSTREAM.
- Se supone que el método
AreSamplesPending
devuelve TRUE siempre que uno o varios fotogramas estén esperando en la cola programada.
En el método IMFVideoPresenter::ProcessMessage, establezca m_fEndStreaming en TRUE y llame a CheckEndOfStream
cuando el EVR envíe el mensaje MFVP_MESSAGE_ENDOFSTREAM:
HRESULT EVRCustomPresenter::ProcessMessage(
MFVP_MESSAGE_TYPE eMessage,
ULONG_PTR ulParam
)
{
HRESULT hr = S_OK;
EnterCriticalSection(&m_ObjectLock);
hr = CheckShutdown();
if (FAILED(hr))
{
goto done;
}
switch (eMessage)
{
// Flush all pending samples.
case MFVP_MESSAGE_FLUSH:
hr = Flush();
break;
// Renegotiate the media type with the mixer.
case MFVP_MESSAGE_INVALIDATEMEDIATYPE:
hr = RenegotiateMediaType();
break;
// The mixer received a new input sample.
case MFVP_MESSAGE_PROCESSINPUTNOTIFY:
hr = ProcessInputNotify();
break;
// Streaming is about to start.
case MFVP_MESSAGE_BEGINSTREAMING:
hr = BeginStreaming();
break;
// Streaming has ended. (The EVR has stopped.)
case MFVP_MESSAGE_ENDSTREAMING:
hr = EndStreaming();
break;
// All input streams have ended.
case MFVP_MESSAGE_ENDOFSTREAM:
// Set the EOS flag.
m_bEndStreaming = TRUE;
// Check if it's time to send the EC_COMPLETE event to the EVR.
hr = CheckEndOfStream();
break;
// Frame-stepping is starting.
case MFVP_MESSAGE_STEP:
hr = PrepareFrameStep(LODWORD(ulParam));
break;
// Cancels frame-stepping.
case MFVP_MESSAGE_CANCELSTEP:
hr = CancelFrameStep();
break;
default:
hr = E_INVALIDARG; // Unknown message. This case should never occur.
break;
}
done:
LeaveCriticalSection(&m_ObjectLock);
return hr;
}
Además, llame a CheckEndOfStream
si el método IMFTransform::ProcessOutput del mezclador devuelve MF_E_TRANSFORM_NEED_MORE_INPUT. Este código de error indica que el mezclador no tiene más muestras de entrada (consulte Procesamiento de la salida).
Avance en los fotogramas
El EVR está diseñado para admitir el avance en los fotogramas en DirectShow y limpieza en Media Foundation. El avance en los fotogramas y la limpieza de fotogramas son conceptualmente similares. En ambos casos, la aplicación solicita un fotograma de vídeo a la vez. Internamente, el moderador usa el mismo mecanismo para implementar ambas características.
El avance en los fotogramas en DirectShow funciona de la siguiente manera:
- La aplicación llama a IVideoFrameStep::Step. El número de pasos se proporciona en el parámetro dwSteps. El EVR envía un mensaje MFVP_MESSAGE_STEP al moderador, donde el parámetro de mensaje (ulParam) es el número de pasos.
- Si la aplicación llama a IVideoFrameStep::CancelStep o cambia el estado del gráfico (en ejecución, en pausa o detenido), el EVR envía un mensaje MFVP_MESSAGE_CANCELSTEP.
La limpieza en Media Foundation funciona de la siguiente manera:
- La aplicación establece la velocidad de reproducción en cero llamando a IMFRateControl::SetRate.
- Para representar un nuevo fotograma, la aplicación llama a IMFMediaSession::Start con la posición deseada. El EVR envía un mensaje MFVP_MESSAGE_STEP con ulParam igual a 1.
- Para detener el limpieza, la aplicación establece la velocidad de reproducción en un valor distinto de cero. El EVR envía el mensaje MFVP_MESSAGE_CANCELSTEP.
Después de recibir el mensaje MFVP_MESSAGE_STEP, el moderador espera a que llegue el fotograma de destino. Si el número de pasos es N, el moderador descarta las muestras siguientes (N - 1) y presenta la muestra número N. Cuando el moderador completa el paso de fotogramas, envía un evento EC_STEP_COMPLETE al EVR con lParam1 establecido en FALSE. Además, si la velocidad de reproducción es cero, el moderador envía un evento EC_SCRUB_TIME. Si el EVR cancela el avance en los fotogramas mientras una operación de avance en los fotogramas sigue pendiente, el moderador envía un evento EC_STEP_COMPLETE con lParam1 establecido en TRUE.
La aplicación puede realizar el avance en los fotogramas o limpiar varias veces, por lo que el moderador puede recibir varios mensajes MFVP_MESSAGE_STEP antes de obtener un mensaje MFVP_MESSAGE_CANCELSTEP. Además, el moderador puede recibir el mensaje MFVP_MESSAGE_STEP antes de que se inicie el reloj o mientras se ejecuta el reloj.
Implementación del avance en los fotogramas
En esta sección se describe un algoritmo para implementar el avance en los fotogramas. El algoritmo de avance en los fotogramas usa las siguientes variables:
step_count. Entero sin signo que especifica el número de pasos de la operación de avance en los fotogramas del fotograma actual.
step_queue. Una cola de punteros IMFSample.
step_state. En cualquier momento, el moderador puede estar en uno de los siguientes estados con respecto al avance en los fotogramas:
State Descripción NOT_STEPPING Sin avance en los fotogramas. WAITING El moderador ha recibido el mensaje MFVP_MESSAGE_STEP, pero el reloj no se ha iniciado. PENDING El moderador ha recibido el mensaje MFVP_MESSAGE_STEP y el reloj se ha iniciado, pero el moderador está esperando recibir el fotograma de destino. SCHEDULED El moderador ha recibido el fotograma de destino y lo ha programado para su presentación, pero el fotograma no se ha presentado. COMPLETAR El moderador ha presentado el fotograma de destino y ha enviado el evento EC_STEP_COMPLETE y está esperando el siguiente mensaje MFVP_MESSAGE_STEP o MFVP_MESSAGE_CANCELSTEP. Estos estados son independientes de los estados del moderador enumerados en la sección Estados del moderador.
Los procedimientos siguientes se definen para el algoritmo de avance en los fotogramas:
Procedimiento PrepareFrameStep
- Incremente step_count.
- Establezca step_state en WAITING.
- Si el reloj se está ejecutando, llame a StartFrameStep.
Procedimiento StartFrameStep
- Si step_state es igual a WAITING, establezca step_state en PENDING. Para cada muestra de step_queue, llame a DeliverFrameStepSample.
- Si step_state es igual a NOT_STEPPING, quite las muestras de step_queue y prográmelas para su presentación.
Procedimiento CompleteFrameStep
- Establezca step_state en COMPLETE.
- Envíe el evento EC_STEP_COMPLETE con lParam1 = FALSE.
- Si la frecuencia de reloj es cero, envíe el evento EC_SCRUB_TIME con la hora de muestra.
Procedimiento DeliverFrameStepSample
- Si la frecuencia de reloj es cero y tiempo de muestra + duración de muestra<tiempo de reloj, descarte la muestra. Salir.
- Si step_state es igual a SCHEDULED o COMPLETE, agregue la muestra a step_queue. Salir.
- Disminuya step_count.
- Si step_count> es 0, descarte la muestra. Salir.
- Si step_state es igual a WAITING, agregue la muestra a step_queue. Salir.
- Programe la muestra para la presentación.
- Establezca step_state en SCHEDULED.
Procedimiento CancelFrameStep
- Establezca step_state en NOT_STEPPING
- Restablezca step_count a cero.
- Si el valor anterior de step_state era WAITING, PENDING o SCHEDULED, envíe EC_STEP_COMPLETE con lParam1 = TRUE.
Llame a estos procedimientos de la manera siguiente:
Mensaje o método del moderador | Procedimiento |
---|---|
Mensaje MFVP_MESSAGE_STEP | PrepareFrameStep |
Mensaje MFVP_MESSAGE_STEP | CancelStep |
IMFClockStateSink::OnClockStart | StartFrameStep |
IMFClockStateSink::OnClockRestart | StartFrameStep |
Devolución de llamada IMFTrackedSample | CompleteFrameStep |
IMFClockStateSink::OnClockStop | CancelFrameStep |
IMFClockStateSink::OnClockSetRate | CancelFrameStep |
En el siguiente gráfico de flujo se muestran los procedimientos del avance en los fotogramas.
Establecer el moderador en el EVR
Después de implementar el moderador, el siguiente paso es configurar el EVR para usarlo.
Establecer el moderador en DirectShow
En una aplicación de DirectShow, establezca el moderador en el EVR de la siguiente manera:
- Cree el filtro de EVR llamando a CoCreateInstance. El CLSID es CLSID_EnhancedVideoRenderer.
- Agregue el EVR al gráfico de filtros.
- Cree una instancia del moderador. El moderador puede admitir la creación de objetos COM estándar a través de IClassFactory, pero esto no es obligatorio.
- Consulte el filtro de EVR para la interfaz IMFVideoRenderer.
- Llame a IMFVideoRenderer::InitializeRenderer.
Establecer el moderador en Media Foundation
En Media Foundation, tiene varias opciones, en función de si crea el receptor multimedia de EVR o el objeto de activación de EVR. Para obtener más información sobre los objetos de activación, consulte Objetos de activación.
Para el receptor multimedia de EVR, haga lo siguiente:
- Llame a MFCreateVideoRenderer para crear el receptor multimedia.
- Cree una instancia del moderador.
- Consulte el receptor multimedia de EVR para la interfaz IMFVideoRenderer.
- Llame a IMFVideoRenderer::InitializeRenderer.
Para el objeto de activación de EVR, haga lo siguiente:
Llame a MFCreateVideoRendererActivate para crear el objeto de activación.
Establezca uno de los atributos siguientes en el objeto de activación:
Atributo Descripción MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE Puntero a un objeto de activación para el moderador.
Con esta marca, debe proporcionar un objeto de activación para el moderador. El objeto de activación debe implementar la interfaz IMFActivate.MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID del moderador.
Con esta marca, el moderador debe admitir la creación de objetos COM estándar a través de IClassFactory.De manera opcional, establezca el atributo MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS en el objeto de activación.
Temas relacionados