Admitir la búsqueda en un filtro de origen
[La característica asociada a esta página, DirectShow, es una característica heredada. Se ha reemplazado por MediaPlayer, IMFMediaEngine y Captura de audio/vídeo en Media Foundation. Esas características se han optimizado para Windows 10 y Windows 11. Microsoft recomienda encarecidamente que el nuevo código use MediaPlayer, IMFMediaEngine y Audio/Video Capture en Media Foundation en lugar de DirectShow, 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 tema se describe cómo implementar la búsqueda en un filtro de origen de Microsoft DirectShow. Usa el ejemplo de filtro de bolas como punto de partida y describe el código adicional necesario para admitir la búsqueda en este filtro.
El ejemplo de filtro de bolas es un filtro de origen que crea una bola de rebote animada. En este artículo se describe cómo agregar funcionalidad de búsqueda a este filtro. Una vez que haya agregado esta funcionalidad, puede representar el filtro en GraphEdit y controlar la bola arrastrando el control deslizante GraphEdit.
Este tema contiene las siguientes secciones:
- Información general sobre la búsqueda en DirectShow
- Información general rápida del filtro de bolas
- Modificar el filtro de bolas para buscar
- Limitaciones de la clase CSourceSeeking
Información general sobre la búsqueda en DirectShow
Una aplicación busca el gráfico de filtros llamando a un método IMediaSeeking en el Administrador de gráficos de filtros. A continuación, el Administrador de gráficos de filtros distribuye la llamada a todos los representadores del gráfico. Cada representador envía la llamada ascendente a través del pin de salida del siguiente filtro ascendente. La llamada viaja ascendente hasta que alcanza un filtro que puede ejecutar el comando seek, normalmente un filtro de origen o un filtro de analizador. En general, el filtro que origina las marcas de tiempo también controla la búsqueda.
Un filtro responde a un comando seek de la siguiente manera:
- El filtro vacía el gráfico. Esto borra los datos obsoletos del grafo, lo que mejora la capacidad de respuesta. De lo contrario, es posible que se entreguen ejemplos almacenados en búfer antes del comando seek.
- El filtro llama a IPin::NewSegment para informar a los filtros de bajada de la nueva hora de detención, la hora de inicio y la velocidad de reproducción.
- A continuación, el filtro establece la marca de discontinuidad en el primer ejemplo después del comando seek.
Las marcas de tiempo comienzan desde cero después de cualquier comando seek (incluidos los cambios de velocidad).
Información general rápida del filtro de bolas
El filtro Ball es un origen de inserción, lo que significa que usa un subproceso de trabajo para entregar muestras de bajada, en lugar de un origen de extracción, que espera pasivamente a que un filtro de bajada solicite muestras. El filtro Ball se crea a partir de la clase CSource y su pin de salida se compila a partir de la clase CSourceStream . La clase CSourceStream crea el subproceso de trabajo que controla el flujo de datos. Este subproceso entra en un bucle que obtiene ejemplos del asignador, los rellena con datos y los entrega de bajada.
La mayoría de las acciones de CSourceStream se producen en el método CSourceStream::FillBuffer , que implementa la clase derivada. El argumento de este método es un puntero al ejemplo que se va a entregar. La implementación del filtro Ball de FillBuffer recupera la dirección del búfer de ejemplo y dibuja directamente en el búfer estableciendo valores de píxeles individuales. (El dibujo se realiza mediante una clase auxiliar, CBall, para que pueda omitir los detalles relativos a profundidades de bits, paletas, etc.).
Modificar el filtro de bolas para buscar
Para que el filtro Ball se pueda buscar, use la clase CSourceSeeking , que está diseñada para implementar la búsqueda en filtros con un pin de salida. Agregue la clase CSourceSeeking a la lista de herencia de la clase CBallStream:
class CBallStream : // Defines the output pin.
public CSourceStream, public CSourceSeeking
Además, deberá agregar un inicializador para CSourceSeeking al constructor de CBallStream:
CSourceSeeking(NAME("SeekBall"), (IPin*)this, phr, &m_cSharedState),
Esta instrucción llama al constructor base para CSourceSeeking. Los parámetros son un nombre, un puntero al pin propietario, un valor HRESULT y la dirección de un objeto de sección crítica. El nombre solo se usa para la depuración y la macro NAME se compila en una cadena vacía en compilaciones comerciales. El pin hereda directamente CSourceSeeking, por lo que el segundo parámetro es un puntero a sí mismo, convertido a un puntero IPin . El valor HRESULT se omite en la versión actual de las clases base; sigue siendo compatible con versiones anteriores. La sección crítica protege los datos compartidos, como la hora de inicio actual, la hora de detención y la velocidad de reproducción.
Agregue la siguiente instrucción dentro del constructor CSourceSeeking :
m_rtStop = 60 * UNITS;
La variable m_rtStop especifica la hora de detención. De forma predeterminada, el valor es _I64_MAX / 2, que es de aproximadamente 14 600 años. La instrucción anterior la establece en un valor más conservador de 60 segundos.
Se deben agregar dos variables de miembro adicionales a CBallStream:
BOOL m_bDiscontinuity; // If true, set the discontinuity flag.
REFERENCE_TIME m_rtBallPosition; // Position of the ball.
Después de cada comando seek, el filtro debe establecer la marca de discontinuidad en el ejemplo siguiente llamando a IMediaSample::SetDiscontinuity. La variable m_bDiscontinuity realizará un seguimiento de esto. La variable m_rtBallPosition especificará la posición de la bola dentro del marco de vídeo. El filtro Ball original calcula la posición del tiempo de la secuencia, pero el tiempo de la secuencia se restablece a cero después de cada comando seek. En una secuencia que se puede buscar, la posición absoluta es independiente del tiempo de la secuencia.
QueryInterface
La clase CSourceSeeking implementa la interfaz IMediaSeeking . Para exponer esta interfaz a los clientes, invalide el método NonDelegatingQueryInterface :
STDMETHODIMP CBallStream::NonDelegatingQueryInterface
(REFIID riid, void **ppv)
{
if( riid == IID_IMediaSeeking )
{
return CSourceSeeking::NonDelegatingQueryInterface( riid, ppv );
}
return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}
El método se denomina "NonDelegating" debido a la forma en que las clases base directShow admiten la agregación del modelo de objetos componentes (COM). Para obtener más información, consulte el tema "Cómo implementar IUnknown" en el SDK de DirectShow.
Buscar métodos
La clase CSourceSeeking mantiene varias variables miembro relacionadas con la búsqueda.
Variable | Descripción | Valor predeterminado |
---|---|---|
m_rtStart | Hora de inicio | Cero |
m_rtStop | Hora de detención | _I64_MAX/2 |
m_dRateSeeking | Velocidad de reproducción | 1.0 |
La implementación de CSourceSeeking de IMediaSeeking::SetPositions actualiza las horas de inicio y detención y, a continuación, llama a dos métodos virtuales puros en la clase derivada, CSourceSeeking::ChangeStart y CSourceSeeking::ChangeStop. La implementación de IMediaSeeking::SetRate es similar: actualiza la velocidad de reproducción y, a continuación, llama al método virtual puro CSourceSeeking::ChangeRate. En cada uno de estos métodos virtuales, el pin debe hacer lo siguiente:
- Llame a IPin::BeginFlush para empezar a vaciar los datos.
- Detenga el subproceso de streaming.
- Llame a IPin::EndFlush.
- Reinicie el subproceso de streaming.
- Llame a IPin::NewSegment.
- Establezca la marca de discontinuidad en el ejemplo siguiente.
El orden de estos pasos es fundamental, ya que el subproceso de streaming puede bloquearse mientras espera a entregar un ejemplo o obtener un nuevo ejemplo. El método BeginFlush garantiza que el subproceso de streaming no esté bloqueado y, por lo tanto, no se interbloquee en el paso 2. La llamada a EndFlush informa a los filtros de bajada para esperar nuevos ejemplos, por lo que no los rechazan cuando el subproceso se inicia de nuevo en el paso 4.
El siguiente método privado realiza los pasos del 1 al 4:
void CBallStream::UpdateFromSeek()
{
if (ThreadExists())
{
DeliverBeginFlush();
// Shut down the thread and stop pushing data.
Stop();
DeliverEndFlush();
// Restart the thread and start pushing data again.
Pause();
}
}
Cuando el subproceso de streaming se inicia de nuevo, llama al método CSourceStream::OnThreadStartPlay . Invalide este método para realizar los pasos 5 y 6:
HRESULT CBallStream::OnThreadStartPlay()
{
m_bDiscontinuity = TRUE;
return DeliverNewSegment(m_rtStart, m_rtStop, m_dRateSeeking);
}
En el método ChangeStart , establezca el tiempo de secuencia en cero y la posición de la bola en la nueva hora de inicio. A continuación, llame a CBallStream::UpdateFromSeek:
HRESULT CBallStream::ChangeStart( )
{
{
CAutoLock lock(CSourceSeeking::m_pLock);
m_rtSampleTime = 0;
m_rtBallPosition = m_rtStart;
}
UpdateFromSeek();
return S_OK;
}
En el método ChangeStop , llame a CBallStream::UpdateFromSeek si la nueva hora de detención es menor que la posición actual. De lo contrario, el tiempo de detención sigue en el futuro, por lo que no es necesario vaciar el gráfico.
HRESULT CBallStream::ChangeStop( )
{
{
CAutoLock lock(CSourceSeeking::m_pLock);
if (m_rtBallPosition < m_rtStop)
{
return S_OK;
}
}
// We're already past the new stop time. Flush the graph.
UpdateFromSeek();
return S_OK;
}
Para los cambios de velocidad, el método CSourceSeeking::SetRate establece m_dRateSeeking en la nueva velocidad (descartando el valor anterior) antes de llamar a ChangeRate. Desafortunadamente, si el autor de la llamada dio una tasa no válida (por ejemplo, menor que cero), es demasiado tarde en el momento en que se llama a ChangeRate . Una solución consiste en invalidar SetRate y comprobar si hay tarifas válidas:
HRESULT CBallStream::SetRate(double dRate)
{
if (dRate <= 1.0)
{
return E_INVALIDARG;
}
{
CAutoLock lock(CSourceSeeking::m_pLock);
m_dRateSeeking = dRate;
}
UpdateFromSeek();
return S_OK;
}
// Now ChangeRate won't ever be called, but it's pure virtual, so it needs
// a dummy implementation.
HRESULT CBallStream::ChangeRate() { return S_OK; }
Dibujo en el búfer
Esta es la versión modificada de CSourceStream::FillBuffer, la rutina que dibuja la bola en cada fotograma:
HRESULT CBallStream::FillBuffer(IMediaSample *pMediaSample)
{
BYTE *pData;
long lDataLen;
pMediaSample->GetPointer(&pData);
lDataLen = pMediaSample->GetSize();
{
CAutoLock cAutoLockShared(&m_cSharedState);
if (m_rtBallPosition >= m_rtStop)
{
// End of the stream.
return S_FALSE;
}
// Draw the ball in its current position.
ZeroMemory( pData, lDataLen );
m_Ball->MoveBall(m_rtBallPosition);
m_Ball->PlotBall(pData, m_BallPixel, m_iPixelSize);
// The sample times are modified by the current rate.
REFERENCE_TIME rtStart, rtStop;
rtStart = static_cast<REFERENCE_TIME>(
m_rtSampleTime / m_dRateSeeking);
rtStop = rtStart + static_cast<int>(
m_iRepeatTime / m_dRateSeeking);
pMediaSample->SetTime(&rtStart, &rtStop);
// Increment for the next loop.
m_rtSampleTime += m_iRepeatTime;
m_rtBallPosition += m_iRepeatTime;
}
pMediaSample->SetSyncPoint(TRUE);
if (m_bDiscontinuity)
{
pMediaSample->SetDiscontinuity(TRUE);
m_bDiscontinuity = FALSE;
}
return NOERROR;
}
Las principales diferencias entre esta versión y la original son las siguientes:
- Como se mencionó anteriormente, la variable m_rtBallPosition se usa para establecer la posición de la bola, en lugar del tiempo de transmisión.
- Después de mantener la sección crítica, el método comprueba si la posición actual supera el tiempo de detención. Si es así, devuelve S_FALSE, que indica a la clase base que deja de enviar datos y entrega una notificación de fin de secuencia.
- Las marcas de tiempo se dividen por la tasa actual.
- Si m_bDiscontinuity es TRUE, el método establece la marca de discontinuidad en el ejemplo.
Hay otra diferencia menor. Dado que la versión original se basa en tener exactamente un búfer, rellena todo el búfer con ceros una vez, cuando comienza el streaming. Después de eso, simplemente borra la pelota de su posición anterior. Sin embargo, esta optimización revela un pequeño error en el filtro ball. Cuando el método CBaseOutputPin::D ecideAllocator llama a IMemInputPin::NotifyAllocator, establece la marca de solo lectura en FALSE. Como resultado, el filtro de bajada es libre de escribir en el búfer. Una solución consiste en invalidar DecideAllocator para que establezca la marca de solo lectura en TRUE. Sin embargo, para simplificar, la nueva versión simplemente quita la optimización por completo. En su lugar, esta versión rellena el búfer con ceros cada vez.
Cambios varios
En la nueva versión, estas dos líneas se quitan del constructor CBall:
m_iRandX = rand();
m_iRandY = rand();
El filtro Ball original usa estos valores para agregar cierta aleatoriedad a la posición inicial de la bola. Para nuestros propósitos, queremos que la pelota sea determinista. Además, algunas variables se han cambiado de objetos CRefTime a REFERENCE_TIME variables. (La clase CRefTime es un contenedor fino para un valor de REFERENCE_TIME ). Por último, la implementación de IQualityControl::Notify se ha modificado ligeramente; Puede consultar el código fuente para obtener más información.
Limitaciones de la clase CSourceSeeking
La clase CSourceSeeking no está pensada para filtros con varios pines de salida, debido a problemas con la comunicación entre patillas. Por ejemplo, imagine un filtro del analizador que recibe una secuencia de audio-vídeo intercalada, divide la secuencia en sus componentes de audio y vídeo, y entrega vídeo de un pin de salida y audio de otro. Ambos pines de salida recibirán todos los comandos seek, pero el filtro solo debe buscar una vez por comando seek. La solución consiste en designar uno de los patillas para controlar la búsqueda y omitir los comandos seek recibidos por el otro pin.
Sin embargo, después del comando seek, ambas patillas deben vaciar los datos. Para complicar aún más las cuestiones, el comando seek se produce en el subproceso de la aplicación, no en el subproceso de streaming. Por lo tanto, debe asegurarse de que ninguna patilla está bloqueada y esperando una llamada IMemInputPin::Receive para devolver, o puede provocar un interbloqueo. Para obtener más información sobre el vaciado seguro para subprocesos en patillas, vea Subprocesos y secciones críticas.
Temas relacionados