Estudo de Caso: Fonte de Mídia MPEG-1
No Microsoft Media Foundation, o objeto que introduz dados de mídia no pipeline de dados é chamado de fonte de mídia. Este tópico analisa detalhadamente o Exemplo de SDK de Origem de Mídia MPEG-1 .
- Pré-requisitos
- Classes C++ usadas na origem MPEG-1
- Manipulador de fluxo de bytes
- Descritor de Apresentação
- Estados de streaming
- Solicitações de exemplo
- Fim do Fluxo
- Operações assíncronas
- Tópicos relacionados
Pré-requisitos
Antes de ler este tópico, você deve entender os seguintes conceitos do Media Foundation:
- Modelo de objeto de origem de mídia
- Métodos de retorno de chamada assíncronos.
- Filas de Trabalho
- Geradores de eventos de mídia
- Buffers de mídia
- Exemplos de mídia
- Tipos de mídia
Você também deve ter uma compreensão básica da arquitetura do Media Foundation, particularmente a função das fontes de mídia no pipeline. (Para obter mais informações, consulte Fontes de mídia.)
Além disso, talvez você queira ler o tópico Escrevendo uma fonte de mídia personalizada, que fornece uma visão geral mais geral das etapas descritas aqui.
Este tópico não reproduz todo o código do exemplo do SDK, pois o exemplo é bastante grande.
Classes C++ usadas na origem MPEG-1
A origem MPEG-1 de exemplo é implementada com as seguintes classes C++:
-
MPEG1ByteStreamHandler
. Implementa o manipulador de fluxo de bytes para a origem da mídia. Dado um fluxo de bytes, o manipulador de fluxo de bytes cria uma instância da origem. -
MPEG1Source
. Implementa a fonte de mídia. -
MPEG1Stream
. Implementa os objetos de fluxo de mídia. A fonte de mídia cria umMPEG1Stream
objeto para cada fluxo de áudio ou vídeo no bitstream MPEG-1. -
Parser
. Analisa o bitstream MPEG-1. Na maioria das vezes, os detalhes dessa classe não são relevantes para as APIs do Media Foundation. - SourceOp, OpQueue: essas duas classes gerenciam operações assíncronas na fonte de mídia. (Consulte Operações assíncronas).
Outras classes auxiliares diversas são descritas posteriormente no tópico.
Manipulador de Byte-Stream
O manipulador de fluxo de bytes é o objeto que cria a fonte de mídia. O manipulador de fluxo de bytes é criado pelo resolvedor de origem; os aplicativos não interagem diretamente com o manipulador de fluxo de bytes. O resolvedor de origem descobre o manipulador de fluxo de bytes procurando no registro. O manipulador é registrado por extensão de nome de arquivo ou tipo MIME. Para a origem MPEG-1, o manipulador de fluxo de bytes é registrado para a extensão de nome de arquivo ".mpg".
Observação
Se você quiser dar suporte a esquemas de URL personalizados, também poderá escrever um manipulador de esquema. A origem MPEG-1 foi projetada para arquivos locais e o Media Foundation já fornece um manipulador de esquema para URLs "file://".
O manipulador de fluxo de bytes implementa a interface IMFByteStreamHandler . Essa interface tem dois métodos mais importantes que devem ser implementados:
- BeginCreateObject. Inicia uma operação assíncrona para criar a fonte de mídia.
- EndCreateObject. Conclui a chamada assíncrona.
Dois outros métodos são opcionais e não são implementados no exemplo do SDK:
- CancelObjectCreation. Cancela o método BeginCreateObject . Esse método é útil para uma fonte de rede que pode ter uma alta latência na inicialização.
- GetMaxNumberOfBytesRequiredForResolution. Obtém o número máximo de bytes que o manipulador lerá do fluxo de origem. Implemente esse método se você souber quantos dados o manipulador de fluxo de bytes pode criar a fonte de mídia. Caso contrário, basta retornar E_NOTIMPL.
Aqui está a implementação do método BeginCreateObject :
HRESULT MPEG1ByteStreamHandler::BeginCreateObject(
/* [in] */ IMFByteStream *pByteStream,
/* [in] */ LPCWSTR pwszURL,
/* [in] */ DWORD dwFlags,
/* [in] */ IPropertyStore *pProps,
/* [out] */ IUnknown **ppIUnknownCancelCookie, // Can be NULL
/* [in] */ IMFAsyncCallback *pCallback,
/* [in] */ IUnknown *punkState // Can be NULL
)
{
if (pByteStream == NULL)
{
return E_POINTER;
}
if (pCallback == NULL)
{
return E_POINTER;
}
if ((dwFlags & MF_RESOLUTION_MEDIASOURCE) == 0)
{
return E_INVALIDARG;
}
HRESULT hr = S_OK;
IMFAsyncResult *pResult = NULL;
MPEG1Source *pSource = NULL;
// Create an instance of the media source.
hr = MPEG1Source::CreateInstance(&pSource);
// Create a result object for the caller's async callback.
if (SUCCEEDED(hr))
{
hr = MFCreateAsyncResult(NULL, pCallback, punkState, &pResult);
}
// Start opening the source. This is an async operation.
// When it completes, the source will invoke our callback
// and then we will invoke the caller's callback.
if (SUCCEEDED(hr))
{
hr = pSource->BeginOpen(pByteStream, this, NULL);
}
if (SUCCEEDED(hr))
{
if (ppIUnknownCancelCookie)
{
*ppIUnknownCancelCookie = NULL;
}
m_pSource = pSource;
m_pSource->AddRef();
m_pResult = pResult;
m_pResult->AddRef();
}
// cleanup
SafeRelease(&pSource);
SafeRelease(&pResult);
return hr;
}
O método executa as seguintes etapas:
- Cria uma nova instância do objeto
MPEG1Source
. - Crie um objeto de resultado assíncrono. Esse objeto é usado posteriormente para invocar o método de retorno de chamada do resolvedor de origem.
- Chama
MPEG1Source::BeginOpen
, um método assíncrono definido naMPEG1Source
classe . - Define ppIUnknownCancelCookie como NULL, o que informa ao chamador que CancelObjectCreation não tem suporte.
O MPEG1Source::BeginOpen
método faz o trabalho real de ler o fluxo de bytes e inicializar o MPEG1Source
objeto. Esse método não faz parte da API pública. Você pode definir qualquer mecanismo entre o manipulador e a fonte de mídia que atenda às suas necessidades. Colocar a maior parte da lógica na fonte de mídia mantém o manipulador de fluxo de bytes relativamente simples.
Resumidamente, BeginOpen
faz o seguinte:
- Chama IMFByteStream::GetCapabilities para verificar se o fluxo de bytes de origem é legível e viável.
- Chama IMFByteStream::BeginRead para iniciar uma solicitação de E/S assíncrona.
O restante da inicialização ocorre de forma assíncrona. A fonte de mídia lê dados suficientes do fluxo para analisar os cabeçalhos de sequência MPEG-1. Em seguida, ele cria um descritor de apresentação, que é o objeto usado para descrever os fluxos de áudio e vídeo no arquivo. (Para obter mais informações, consulte Descritor de apresentação.) Quando a BeginOpen
operação for concluída, o manipulador de fluxo de bytes invocará o método de retorno de chamada do resolvedor de origem. Nesse ponto, o resolvedor de origem chama IMFByteStreamHandler::EndCreateObject. O método EndCreateObject retorna o status da operação.
HRESULT MPEG1ByteStreamHandler::EndCreateObject(
/* [in] */ IMFAsyncResult *pResult,
/* [out] */ MF_OBJECT_TYPE *pObjectType,
/* [out] */ IUnknown **ppObject)
{
if (pResult == NULL || pObjectType == NULL || ppObject == NULL)
{
return E_POINTER;
}
HRESULT hr = S_OK;
*pObjectType = MF_OBJECT_INVALID;
*ppObject = NULL;
hr = pResult->GetStatus();
if (SUCCEEDED(hr))
{
*pObjectType = MF_OBJECT_MEDIASOURCE;
assert(m_pSource != NULL);
hr = m_pSource->QueryInterface(IID_PPV_ARGS(ppObject));
}
SafeRelease(&m_pSource);
SafeRelease(&m_pResult);
return hr;
}
Se ocorrer um erro a qualquer momento durante esse processo, o retorno de chamada será invocado com um erro status código.
Descritor de Apresentação
O descritor de apresentação descreve o conteúdo do arquivo MPEG-1, incluindo as seguintes informações:
- O número dos fluxos.
- O formato de cada fluxo.
- Identificadores de fluxo.
- A seleção status de cada fluxo (selecionado ou desmarcado).
Em termos da arquitetura do Media Foundation, o descritor de apresentação contém um ou mais descritores de fluxo. Cada descritor de fluxo contém um manipulador de tipo de mídia, que é usado para obter ou definir tipos de mídia no fluxo. O Media Foundation fornece implementações de estoque para o descritor de apresentação e o descritor de fluxo; eles são adequados para a maioria das fontes de mídia.
Para criar um descritor de apresentação, execute as seguintes etapas:
- Para cada fluxo:
- Forneça uma ID de fluxo e uma matriz de possíveis tipos de mídia. Se o fluxo der suporte a mais de um tipo de mídia, solicite a lista de tipos de mídia por preferência, se houver. (Coloque o tipo ideal em primeiro lugar e o tipo menos ideal por último.)
- Chame MFCreateStreamDescriptor para criar o descritor de fluxo.
- Chame IMFStreamDescriptor::GetMediaTypeHandler no descritor de fluxo recém-criado.
- Chame IMFMediaTypeHandler::SetCurrentMediaType para definir o formato padrão para o fluxo. Se houver mais de um tipo de mídia, você geralmente deverá definir o primeiro tipo na lista.
- Chame MFCreatePresentationDescriptor e passe a matriz de ponteiros do descritor de fluxo.
- Para cada fluxo, chame IMFPresentationDescriptor::SelectStream ou DeselectStream para definir o estado de seleção padrão. Se houver mais de um fluxo do mesmo tipo (áudio ou vídeo), apenas um deverá ser selecionado por padrão.
O MPEG1Source
objeto cria o descritor de apresentação em seu InitPresentationDescriptor
método:
HRESULT MPEG1Source::InitPresentationDescriptor()
{
HRESULT hr = S_OK;
DWORD cStreams = 0;
assert(m_pPresentationDescriptor == NULL);
assert(m_state == STATE_OPENING);
if (m_pHeader == NULL)
{
return E_FAIL;
}
// Get the number of streams, as declared in the MPEG-1 header, skipping
// any streams with an unsupported format.
for (DWORD i = 0; i < m_pHeader->cStreams; i++)
{
if (IsStreamTypeSupported(m_pHeader->streams[i].type))
{
cStreams++;
}
}
// How many streams do we actually have?
if (cStreams > m_streams.GetCount())
{
// Keep reading data until we have seen a packet for each stream.
return S_OK;
}
// We should never create a stream we don't support.
assert(cStreams == m_streams.GetCount());
// Ready to create the presentation descriptor.
// Create an array of IMFStreamDescriptor pointers.
IMFStreamDescriptor **ppSD =
new (std::nothrow) IMFStreamDescriptor*[cStreams];
if (ppSD == NULL)
{
hr = E_OUTOFMEMORY;
goto done;
}
ZeroMemory(ppSD, cStreams * sizeof(IMFStreamDescriptor*));
// Fill the array by getting the stream descriptors from the streams.
for (DWORD i = 0; i < cStreams; i++)
{
hr = m_streams[i]->GetStreamDescriptor(&ppSD[i]);
if (FAILED(hr))
{
goto done;
}
}
// Create the presentation descriptor.
hr = MFCreatePresentationDescriptor(cStreams, ppSD,
&m_pPresentationDescriptor);
if (FAILED(hr))
{
goto done;
}
// Select the first video stream (if any).
for (DWORD i = 0; i < cStreams; i++)
{
GUID majorType = GUID_NULL;
hr = GetStreamMajorType(ppSD[i], &majorType);
if (FAILED(hr))
{
goto done;
}
if (majorType == MFMediaType_Video)
{
hr = m_pPresentationDescriptor->SelectStream(i);
if (FAILED(hr))
{
goto done;
}
break;
}
}
// Switch state from "opening" to stopped.
m_state = STATE_STOPPED;
// Invoke the async callback to complete the BeginOpen operation.
hr = CompleteOpen(S_OK);
done:
// clean up:
if (ppSD)
{
for (DWORD i = 0; i < cStreams; i++)
{
SafeRelease(&ppSD[i]);
}
delete [] ppSD;
}
return hr;
}
O aplicativo obtém o descritor de apresentação chamando IMFMediaSource::CreatePresentationDescriptor. Esse método cria uma cópia superficial do descritor de apresentação chamando IMFPresentationDescriptor::Clone. (A cópia contém ponteiros para os descritores de fluxo originais.) O aplicativo pode usar o descritor de apresentação para definir o tipo de mídia, selecionar um fluxo ou desmarcar um fluxo.
Opcionalmente, descritores de apresentação e descritores de fluxo podem conter atributos que fornecem informações adicionais sobre a origem. Para obter uma lista desses atributos, consulte os seguintes tópicos:
Um atributo merece menção especiais: o atributo MF_PD_DURATION contém a duração total da origem. Defina esse atributo se você souber a duração antecipadamente; por exemplo, a duração pode ser especificada nos cabeçalhos de arquivo, dependendo do formato do arquivo. O aplicativo pode exibir esse valor ou usá-lo para definir uma barra de progresso ou barra de busca.
Estados de streaming
Uma fonte de mídia define os seguintes estados:
Estado | Descrição |
---|---|
Iniciado | A origem aceita e processa solicitações de exemplo. |
Em Pausa | A origem aceita solicitações de exemplo, mas não as processa. As solicitações são enfileiradas até que a origem seja iniciada. |
Stopped. | A origem rejeita solicitações de exemplo. |
Iniciar
O método IMFMediaSource::Start inicia a fonte de mídia. Ele usa os seguintes parâmetros:
- Um descritor de apresentação.
- Um GUID de formato de tempo.
- Uma posição inicial.
O aplicativo deve obter o descritor de apresentação chamando CreatePresentationDescriptor na origem. Não há nenhum mecanismo definido para validar um descritor de apresentação. Se o aplicativo especificar o descritor de apresentação incorreto, os resultados serão indefinidos.
O GUID de formato de tempo especifica como interpretar a posição inicial. O formato padrão é unidades de 100 nanossegundos (ns), indicadas por GUID_NULL. Cada fonte de mídia deve dar suporte a unidades de 100 ns. Opcionalmente, uma fonte pode dar suporte a outras unidades de tempo, como o número do quadro ou o código de tempo. No entanto, não há uma maneira padrão de consultar uma fonte de mídia para obter a lista de formatos de tempo compatíveis.
A posição inicial é fornecida como UM PROPVARIANT, permitindo diferentes tipos de dados, dependendo do formato de hora. Para 100-ns, o tipo PROPVARIANT é VT_I8 ou VT_EMPTY. Se VT_I8, o PROPVARIANT conterá a posição inicial em unidades de 100 ns. O valor VT_EMPTY tem o significado especial "iniciar na posição atual".
Implemente o método Start da seguinte maneira:
- Validar parâmetros e estado:
- Verifique se há parâmetros NULL .
- Verifique o GUID de formato de hora. Se o valor for inválido, retorne MF_E_UNSUPPORTED_TIME_FORMAT.
- Verifique o tipo de dados do PROPVARIANT que mantém a posição inicial.
- Valide a posição inicial. Se for inválido, retorne MF_E_INVALIDREQUEST.
- Se a origem tiver sido desligada, retorne MF_E_SHUTDOWN.
- Se nenhum erro ocorrer na etapa 1, enfileira uma operação assíncrona. Tudo após essa etapa ocorre em um thread de fila de trabalho.
- Para cada fluxo:
Verifique se o fluxo já está ativo em uma solicitação Inicial anterior.
Chame IMFPresentationDescriptor::GetStreamDescriptorByIndex para marcar se o aplicativo selecionou ou desmarcada o fluxo.
Se um fluxo selecionado anteriormente for desmarcado, libere todos os exemplos não entregues para esse fluxo.
Se o fluxo estiver ativo, a fonte de mídia (não o fluxo) enviará um dos seguintes eventos:
- Ele enviará MEUpdatedStream se o fluxo estiver ativo anteriormente.
- Caso contrário, ele enviará MENewStream.
Para ambos os eventos, os dados do evento são o ponteiro IMFMediaStream para o fluxo.
Se a origem estiver reiniciando do estado em pausa, pode haver solicitações de exemplo pendentes. Nesse caso, entregue-os agora.
Se a origem estiver buscando uma nova posição, cada objeto de fluxo enviará um evento MEStreamSeeked . Caso contrário, cada fluxo enviará um evento MEStreamStarted .
- Se a origem estiver buscando uma nova posição, a fonte de mídia enviará um evento MESourceSeeked . Caso contrário, ele enviará um evento MESourceStarted .
Se ocorrer um erro a qualquer momento após a etapa 2, a origem enviará um evento MESourceStarted com um código de erro. Isso alerta o aplicativo de que o método Start falhou de forma assíncrona.
O código a seguir mostra as etapas 1 a 2:
HRESULT MPEG1Source::Start(
IMFPresentationDescriptor* pPresentationDescriptor,
const GUID* pguidTimeFormat,
const PROPVARIANT* pvarStartPos
)
{
HRESULT hr = S_OK;
SourceOp *pAsyncOp = NULL;
// Check parameters.
// Start position and presentation descriptor cannot be NULL.
if (pvarStartPos == NULL || pPresentationDescriptor == NULL)
{
return E_INVALIDARG;
}
// Check the time format.
if ((pguidTimeFormat != NULL) && (*pguidTimeFormat != GUID_NULL))
{
// Unrecognized time format GUID.
return MF_E_UNSUPPORTED_TIME_FORMAT;
}
// Check the data type of the start position.
if ((pvarStartPos->vt != VT_I8) && (pvarStartPos->vt != VT_EMPTY))
{
return MF_E_UNSUPPORTED_TIME_FORMAT;
}
EnterCriticalSection(&m_critSec);
// Check if this is a seek request. This sample does not support seeking.
if (pvarStartPos->vt == VT_I8)
{
// If the current state is STOPPED, then position 0 is valid.
// Otherwise, the start position must be VT_EMPTY (current position).
if ((m_state != STATE_STOPPED) || (pvarStartPos->hVal.QuadPart != 0))
{
hr = MF_E_INVALIDREQUEST;
goto done;
}
}
// Fail if the source is shut down.
hr = CheckShutdown();
if (FAILED(hr))
{
goto done;
}
// Fail if the source was not initialized yet.
hr = IsInitialized();
if (FAILED(hr))
{
goto done;
}
// Perform a basic check on the caller's presentation descriptor.
hr = ValidatePresentationDescriptor(pPresentationDescriptor);
if (FAILED(hr))
{
goto done;
}
// The operation looks OK. Complete the operation asynchronously.
hr = SourceOp::CreateStartOp(pPresentationDescriptor, &pAsyncOp);
if (FAILED(hr))
{
goto done;
}
hr = pAsyncOp->SetData(*pvarStartPos);
if (FAILED(hr))
{
goto done;
}
hr = QueueOperation(pAsyncOp);
done:
SafeRelease(&pAsyncOp);
LeaveCriticalSection(&m_critSec);
return hr;
}
As etapas restantes são mostradas no próximo exemplo:
HRESULT MPEG1Source::DoStart(StartOp *pOp)
{
assert(pOp->Op() == SourceOp::OP_START);
IMFPresentationDescriptor *pPD = NULL;
IMFMediaEvent *pEvent = NULL;
HRESULT hr = S_OK;
LONGLONG llStartOffset = 0;
BOOL bRestartFromCurrentPosition = FALSE;
BOOL bSentEvents = FALSE;
hr = BeginAsyncOp(pOp);
// Get the presentation descriptor from the SourceOp object.
// This is the PD that the caller passed into the Start() method.
// The PD has already been validated.
if (SUCCEEDED(hr))
{
hr = pOp->GetPresentationDescriptor(&pPD);
}
// Because this sample does not support seeking, the start
// position must be 0 (from stopped) or "current position."
// If the sample supported seeking, we would need to get the
// start position from the PROPVARIANT data contained in pOp.
if (SUCCEEDED(hr))
{
// Select/deselect streams, based on what the caller set in the PD.
// This method also sends the MENewStream/MEUpdatedStream events.
hr = SelectStreams(pPD, pOp->Data());
}
if (SUCCEEDED(hr))
{
m_state = STATE_STARTED;
// Queue the "started" event. The event data is the start position.
hr = m_pEventQueue->QueueEventParamVar(
MESourceStarted,
GUID_NULL,
S_OK,
&pOp->Data()
);
}
if (FAILED(hr))
{
// Failure. Send the error code to the application.
// Note: It's possible that QueueEvent itself failed, in which case it
// is likely to fail again. But there is no good way to recover in
// that case.
(void)m_pEventQueue->QueueEventParamVar(
MESourceStarted, GUID_NULL, hr, NULL);
}
CompleteAsyncOp(pOp);
SafeRelease(&pEvent);
SafeRelease(&pPD);
return hr;
}
Pausar
O método IMFMediaSource::P ause pausa a fonte de mídia. Implemente esse método da seguinte maneira:
- Enfileirar uma operação assíncrona.
- Cada fluxo ativo envia um evento MEStreamPaused .
- A fonte de mídia envia um evento MESourcePaused .
Durante a pausa, a origem enfileira solicitações de exemplo sem processá-las. (Consulte Solicitações de exemplo.)
Stop
O método IMFMediaSource::Stop interrompe a fonte de mídia. Implemente esse método da seguinte maneira:
- Enfileirar uma operação assíncrona.
- Cada fluxo ativo envia um evento MEStreamStopped .
- Limpe todos os exemplos e solicitações de exemplo enfileirados.
- A fonte de mídia envia um evento MESourceStopped .
Enquanto está parada, a origem rejeita todas as solicitações de exemplos.
Se a origem for interrompida enquanto uma solicitação de E/S estiver em andamento, a solicitação de E/S poderá ser concluída depois que a origem entrar no estado parado. Nesse caso, a origem deve descartar o resultado dessa solicitação de E/S.
Solicitações de Amostra
O Media Foundation usa um modelo de pull , no qual o pipeline solicita exemplos da fonte de mídia. Isso difere do modelo usado pelo DirectShow, no qual as fontes "efetuam push" de exemplos.
Para solicitar um novo exemplo, o pipeline do Media Foundation chama IMFMediaStream::RequestSample. Esse método usa um ponteiro IUnknown que representa um objeto de token . A implementação do objeto de token cabe ao chamador; ele simplesmente fornece uma maneira de o chamador acompanhar solicitações de exemplo. O parâmetro de token também pode ser NULL.
Supondo que a origem use solicitações de E/S assíncronas para ler dados, a geração de exemplo não será sincronizada com solicitações de exemplo. Para sincronizar solicitações de exemplo com a geração de exemplo, uma fonte de mídia faz o seguinte:
- Os tokens de solicitação são colocados em uma fila.
- À medida que os exemplos são gerados, eles são colocados em uma segunda fila.
- A fonte de mídia conclui uma solicitação de exemplo extraindo um token de solicitação da primeira fila e um exemplo da segunda fila.
- A fonte de mídia envia um evento MEMediaSample. O evento contém um ponteiro para o exemplo e o exemplo contém um ponteiro para o token.
O diagrama a seguir mostra a relação entre o evento MEMediaSample , o exemplo e o token de solicitação.
A origem MPEG-1 de exemplo implementa esse processo da seguinte maneira:
- O método RequestSample coloca a solicitação em uma fila FIFO.
- À medida que as solicitações de E/S são concluídas, a fonte de mídia cria novos exemplos e as coloca em uma segunda fila FIFO. (Essa fila tem um tamanho máximo, para impedir que a origem leia muito à frente.)
- Sempre que ambas as filas tiverem pelo menos um item (uma solicitação e uma amostra), a fonte de mídia concluirá a primeira solicitação da fila de solicitação enviando o primeiro exemplo da fila de exemplo.
- Para entregar um exemplo, o objeto stream (não o objeto de origem) envia um evento MEMediaSample .
- Os dados do evento são um ponteiro para a interface IMFSample da amostra .
- Se a solicitação incluir um token, anexe o token ao exemplo definindo o atributo MFSampleExtension_Token no exemplo.
Neste ponto, há três possibilidades:
- Há outro exemplo na fila de exemplo, mas nenhuma solicitação correspondente.
- Há uma solicitação, mas nenhuma amostra.
- Ambas as filas estão vazias; não há exemplos nem solicitações.
Se a fila de exemplo estiver vazia, a origem verificará o final do fluxo (consulte Fim do Fluxo). Caso contrário, ele iniciará outra solicitação de E/S para dados. Se ocorrer algum erro durante esse processo, o fluxo enviará um evento MEError .
O código a seguir implementa o método IMFMediaStream::RequestSample :
HRESULT MPEG1Stream::RequestSample(IUnknown* pToken)
{
HRESULT hr = S_OK;
IMFMediaSource *pSource = NULL;
// Hold the media source object's critical section.
SourceLock lock(m_pSource);
hr = CheckShutdown();
if (FAILED(hr))
{
goto done;
}
if (m_state == STATE_STOPPED)
{
hr = MF_E_INVALIDREQUEST;
goto done;
}
if (!m_bActive)
{
// If the stream is not active, it should not get sample requests.
hr = MF_E_INVALIDREQUEST;
goto done;
}
if (m_bEOS && m_Samples.IsEmpty())
{
// This stream has already reached the end of the stream, and the
// sample queue is empty.
hr = MF_E_END_OF_STREAM;
goto done;
}
hr = m_Requests.InsertBack(pToken);
if (FAILED(hr))
{
goto done;
}
// Dispatch the request.
hr = DispatchSamples();
if (FAILED(hr))
{
goto done;
}
done:
if (FAILED(hr) && (m_state != STATE_SHUTDOWN))
{
// An error occurred. Send an MEError even from the source,
// unless the source is already shut down.
hr = m_pSource->QueueEvent(MEError, GUID_NULL, hr, NULL);
}
return hr;
}
O DispatchSamples
método extrai exemplos da fila de exemplo, corresponde a eles com solicitações de exemplo pendentes e enfileira eventos MEMediaSample :
HRESULT MPEG1Stream::DispatchSamples()
{
HRESULT hr = S_OK;
BOOL bNeedData = FALSE;
BOOL bEOS = FALSE;
SourceLock lock(m_pSource);
// An I/O request can complete after the source is paused, stopped, or
// shut down. Do not deliver samples unless the source is running.
if (m_state != STATE_STARTED)
{
return S_OK;
}
IMFSample *pSample = NULL;
IUnknown *pToken = NULL;
// Deliver as many samples as we can.
while (!m_Samples.IsEmpty() && !m_Requests.IsEmpty())
{
// Pull the next sample from the queue.
hr = m_Samples.RemoveFront(&pSample);
if (FAILED(hr))
{
goto done;
}
// Pull the next request token from the queue. Tokens can be NULL.
hr = m_Requests.RemoveFront(&pToken);
if (FAILED(hr))
{
goto done;
}
if (pToken)
{
// Set the token on the sample.
hr = pSample->SetUnknown(MFSampleExtension_Token, pToken);
if (FAILED(hr))
{
goto done;
}
}
// Send an MEMediaSample event with the sample.
hr = m_pEventQueue->QueueEventParamUnk(
MEMediaSample, GUID_NULL, S_OK, pSample);
if (FAILED(hr))
{
goto done;
}
SafeRelease(&pSample);
SafeRelease(&pToken);
}
if (m_Samples.IsEmpty() && m_bEOS)
{
// The sample queue is empty AND we have reached the end of the source
// stream. Notify the pipeline by sending the end-of-stream event.
hr = m_pEventQueue->QueueEventParamVar(
MEEndOfStream, GUID_NULL, S_OK, NULL);
if (FAILED(hr))
{
goto done;
}
// Notify the source. It will send the end-of-presentation event.
hr = m_pSource->QueueAsyncOperation(SourceOp::OP_END_OF_STREAM);
if (FAILED(hr))
{
goto done;
}
}
else if (NeedsData())
{
// The sample queue is empty; the request queue is not empty; and we
// have not reached the end of the stream. Ask for more data.
hr = m_pSource->QueueAsyncOperation(SourceOp::OP_REQUEST_DATA);
if (FAILED(hr))
{
goto done;
}
}
done:
if (FAILED(hr) && (m_state != STATE_SHUTDOWN))
{
// An error occurred. Send an MEError even from the source,
// unless the source is already shut down.
m_pSource->QueueEvent(MEError, GUID_NULL, hr, NULL);
}
SafeRelease(&pSample);
SafeRelease(&pToken);
return S_OK;
}
O DispatchSamples
método é chamado nas seguintes circunstâncias:
- Dentro do método RequestSample .
- Quando a fonte de mídia é reiniciada do estado em pausa.
- Quando uma solicitação de E/S é concluída.
Fim do Fluxo
Quando um fluxo não tem mais dados e todos os exemplos desse fluxo foram entregues, os objetos de fluxo enviam um evento MEEndOfStream .
Quando todos os fluxos ativos são concluídos, a fonte de mídia envia um evento MEEndOfPresentation .
Operações assíncronas
Talvez a parte mais difícil de escrever uma fonte de mídia seja entender o modelo assíncrono da Media Foundation.
Todos os métodos em uma fonte de mídia que controlam o streaming são assíncronos. Em cada caso, o método faz alguma validação inicial, como verificar parâmetros. Em seguida, a origem envia o restante do trabalho para uma fila de trabalho. Após a conclusão da operação, a fonte de mídia envia um evento de volta ao chamador, por meio da interface IMFMediaEventGenerator da fonte de mídia. Portanto, é importante entender as filas de trabalho.
Para colocar um item em uma fila de trabalho, você pode chamar MFPutWorkItem ou MFPutWorkItemEx. A origem MPEG-1 usa MFPutWorkItem, mas as duas funções fazem a mesma coisa. A função MFPutWorkItem usa os seguintes parâmetros:
- Um valor DWORD que identifica a fila de trabalho. Você pode criar uma fila de trabalho privada ou usar MFASYNC_CALLBACK_QUEUE_STANDARD.
- Um ponteiro para a interface IMFAsyncCallback . Essa interface de retorno de chamada é invocada para executar o trabalho.
- Um objeto de estado opcional, que deve implementar IUnknown.
A fila de trabalho é atendida por um ou mais threads de trabalho que extraem continuamente o próximo item de trabalho da fila e invocam o método IMFAsyncCallback::Invoke da interface de retorno de chamada.
Não há garantia de que os itens de trabalho sejam executados na mesma ordem em que você os colocou na fila. Lembre-se de que mais de um thread pode atender à mesma fila de trabalho, portanto, invocar chamadas pode se sobrepor ou ocorrer fora de ordem. Portanto, cabe à fonte de mídia manter o estado interno correto enviando itens da fila de trabalho na ordem certa. Somente quando a operação anterior for concluída a origem iniciará a próxima operação.
Para representar operações pendentes, a origem MPEG-1 define uma classe chamada SourceOp
:
// Represents a request for an asynchronous operation.
class SourceOp : public IUnknown
{
public:
enum Operation
{
OP_START,
OP_PAUSE,
OP_STOP,
OP_REQUEST_DATA,
OP_END_OF_STREAM
};
static HRESULT CreateOp(Operation op, SourceOp **ppOp);
static HRESULT CreateStartOp(IMFPresentationDescriptor *pPD, SourceOp **ppOp);
// IUnknown
STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
SourceOp(Operation op);
virtual ~SourceOp();
HRESULT SetData(const PROPVARIANT& var);
Operation Op() const { return m_op; }
const PROPVARIANT& Data() { return m_data;}
protected:
long m_cRef; // Reference count.
Operation m_op;
PROPVARIANT m_data; // Data for the operation.
};
A Operation
enumeração identifica qual operação está pendente. A classe também contém um PROPVARIANT para transmitir quaisquer dados adicionais para a operação.
Fila de Operações
Para serializar operações, a fonte de mídia mantém uma fila de SourceOp
objetos. Ele usa uma classe auxiliar para gerenciar a fila:
template <class OP_TYPE>
class OpQueue : public IUnknown
{
public:
typedef ComPtrList<OP_TYPE> OpList;
HRESULT QueueOperation(OP_TYPE *pOp);
protected:
HRESULT ProcessQueue();
HRESULT ProcessQueueAsync(IMFAsyncResult *pResult);
virtual HRESULT DispatchOperation(OP_TYPE *pOp) = 0;
virtual HRESULT ValidateOperation(OP_TYPE *pOp) = 0;
OpQueue(CRITICAL_SECTION& critsec)
: m_OnProcessQueue(this, &OpQueue::ProcessQueueAsync),
m_critsec(critsec)
{
}
virtual ~OpQueue()
{
}
protected:
OpList m_OpQueue; // Queue of operations.
CRITICAL_SECTION& m_critsec; // Protects the queue state.
AsyncCallback<OpQueue> m_OnProcessQueue; // ProcessQueueAsync callback.
};
A OpQueue
classe foi projetada para ser herdada pelo componente que executa itens de trabalho assíncronos. O parâmetro de modelo OP_TYPE é o tipo de objeto usado para representar itens de trabalho na fila— nesse caso, OP_TYPE será SourceOp
. A OpQueue
classe implementa os seguintes métodos:
-
QueueOperation
coloca um novo item na fila. -
ProcessQueue
envia a próxima operação da fila. Esse método é assíncrono. -
ProcessQueueAsync
conclui o método assíncronoProcessQueue
.
Outros dois métodos devem ser implementados pela classe derivada:
-
ValidateOperation
verifica se é válido executar uma operação especificada, dado o estado atual da fonte de mídia. -
DispatchOperation
executa o item de trabalho assíncrono.
A fila de operações é usada da seguinte maneira:
- O pipeline do Media Foundation chama um método assíncrono na fonte de mídia, como IMFMediaSource::Start.
- O método assíncrono chama
QueueOperation
, que coloca a operação Iniciar na fila e chamaProcessQueue
(na forma de umSourceOp
objeto). -
ProcessQueue
chama MFPutWorkItem. - O thread da fila de trabalho chama
ProcessQueueAsync
. - O
ProcessQueueAsync
método chamaValidateOperation
eDispatchOperation
.
O código a seguir enfileira uma nova operação na fonte MPEG-1:
template <class OP_TYPE>
HRESULT OpQueue<OP_TYPE>::QueueOperation(OP_TYPE *pOp)
{
HRESULT hr = S_OK;
EnterCriticalSection(&m_critsec);
hr = m_OpQueue.InsertBack(pOp);
if (SUCCEEDED(hr))
{
hr = ProcessQueue();
}
LeaveCriticalSection(&m_critsec);
return hr;
}
O código a seguir processa a fila:
template <class OP_TYPE>
HRESULT OpQueue<OP_TYPE>::ProcessQueue()
{
HRESULT hr = S_OK;
if (m_OpQueue.GetCount() > 0)
{
hr = MFPutWorkItem(
MFASYNC_CALLBACK_QUEUE_STANDARD, // Use the standard work queue.
&m_OnProcessQueue, // Callback method.
NULL // State object.
);
}
return hr;
}
O ValidateOperation
método verifica se a origem MPEG-1 pode expedir a próxima operação na fila. Se outra operação estiver em andamento, ValidateOperation
retornará MF_E_NOTACCEPTING. Isso garante que DispatchOperation
não seja chamado enquanto houver outra operação pendente.
HRESULT MPEG1Source::ValidateOperation(SourceOp *pOp)
{
if (m_pCurrentOp != NULL)
{
return MF_E_NOTACCEPTING;
}
return S_OK;
}
O método DispatchOperation alterna o tipo de operação:
//-------------------------------------------------------------------
// DispatchOperation
//
// Performs the asynchronous operation indicated by pOp.
//
// NOTE:
// This method implements the pure-virtual OpQueue::DispatchOperation
// method. It is always called from a work-queue thread.
//-------------------------------------------------------------------
HRESULT MPEG1Source::DispatchOperation(SourceOp *pOp)
{
EnterCriticalSection(&m_critSec);
HRESULT hr = S_OK;
if (m_state == STATE_SHUTDOWN)
{
LeaveCriticalSection(&m_critSec);
return S_OK; // Already shut down, ignore the request.
}
switch (pOp->Op())
{
// IMFMediaSource methods:
case SourceOp::OP_START:
hr = DoStart((StartOp*)pOp);
break;
case SourceOp::OP_STOP:
hr = DoStop(pOp);
break;
case SourceOp::OP_PAUSE:
hr = DoPause(pOp);
break;
// Operations requested by the streams:
case SourceOp::OP_REQUEST_DATA:
hr = OnStreamRequestSample(pOp);
break;
case SourceOp::OP_END_OF_STREAM:
hr = OnEndOfStream(pOp);
break;
default:
hr = E_UNEXPECTED;
}
if (FAILED(hr))
{
StreamingError(hr);
}
LeaveCriticalSection(&m_critSec);
return hr;
}
Para resumir:
- O pipeline chama um método assíncrono, como IMFMediaSource::Start.
- O método assíncrono chama
OpQueue::QueueOperation
, passando um ponteiro para umSourceOp
objeto . - O
QueueOperation
método coloca a operação na fila m_OpQueue e chamaOpQueue::ProcessQueue
. - O
ProcessQueue
método chama MFPutWorkItem. A partir desse ponto, tudo acontece em um thread de fila de trabalho do Media Foundation. O método assíncrono retorna ao chamador. - O thread da fila de trabalho chama o
OpQueue::ProcessQueueAsync
método . - O
ProcessQueueAsync
método chamaMPEG1Source:ValidateOperation
para validar a operação. - O
ProcessQueueAsync
método chamaMPEG1Source::DispatchOperation
para processar a operação.
Há vários benefícios para esse design:
- Os métodos são assíncronos, portanto, eles não bloqueiam o thread do aplicativo de chamada.
- As operações são expedidas em um thread de fila de trabalho do Media Foundation, que é compartilhado entre componentes de pipeline. Portanto, a fonte de mídia não cria seu próprio thread, reduzindo o número total de threads criados.
- A fonte de mídia não bloqueia enquanto aguarda a conclusão das operações. Isso reduz a chance de uma fonte de mídia causar acidentalmente um deadlock e ajuda a reduzir a alternância de contexto.
- A fonte de mídia pode usar E/S assíncrona para ler o arquivo de origem (chamando IMFByteStream::BeginRead). A fonte de mídia não precisa ser bloqueada enquanto aguarda a conclusão da rotina de E/S.
Se você seguir o padrão mostrado no exemplo do SDK, poderá se concentrar nos detalhes específicos da fonte de mídia.
Tópicos relacionados