다음을 통해 공유


원본 필터에서 검색 지원

[이 페이지와 연결된 기능인 DirectShow는 레거시 기능입니다. MediaPlayer, IMFMediaEngine 및 Media Foundation의 오디오/비디오 캡처로 대체되었습니다. 이러한 기능은 Windows 10 및 Windows 11 최적화되었습니다. 가능한 경우 새 코드가 DirectShow 대신 Media Foundation에서 MediaPlayer, IMFMediaEngine오디오/비디오 캡처를 사용하는 것이 좋습니다. 가능한 경우 레거시 API를 사용하는 기존 코드를 다시 작성하여 새 API를 사용하도록 제안합니다.]

이 항목에서는 Microsoft DirectShow 원본 필터에서 검색을 구현하는 방법을 설명합니다. 볼 필터 샘플을 시작점으로 사용하고 이 필터에서 검색을 지원하는 데 필요한 추가 코드를 설명합니다.

볼 필터 샘플은 애니메이션 바운싱 볼을 만드는 소스 필터입니다. 이 문서에서는 이 필터에 검색 기능을 추가하는 방법을 설명합니다. 이 기능을 추가하면 GraphEdit에서 필터를 렌더링하고 GraphEdit 슬라이더를 끌어 공을 제어할 수 있습니다.

이 항목에는 다음과 같은 섹션이 포함되어 있습니다.

DirectShow에서 검색 개요

애플리케이션은 필터 그래프 관리자에서 IMediaSeeking 메서드를 호출하여 필터 그래프를 찾습니다. 필터 그래프 관리자는 그래프의 모든 렌더러에 호출을 배포합니다. 각 렌더러는 다음 업스트림 필터의 출력 핀을 통해 업스트림 호출을 보냅니다. 호출은 seek 명령(일반적으로 원본 필터 또는 파서 필터)을 실행할 수 있는 필터에 도달할 때까지 업스트림 이동합니다. 일반적으로 타임스탬프를 시작하는 필터도 검색을 처리합니다.

필터는 다음과 같이 seek 명령에 응답합니다.

  1. 필터는 그래프를 플러시합니다. 이렇게 하면 그래프에서 부실 데이터가 지워지며 응답성이 향상됩니다. 그렇지 않으면 seek 명령 이전에 버퍼링된 샘플이 배달될 수 있습니다.
  2. 필터는 IPin::NewSegment 를 호출하여 다운스트림 필터에 새 중지 시간, 시작 시간 및 재생 속도를 알릴 수 있습니다.
  3. 그런 다음 필터는 seek 명령 뒤의 첫 번째 샘플에서 불연속성 플래그를 설정합니다.

타임스탬프는 모든 seek 명령(속도 변경 포함) 후에 0부터 시작합니다.

볼 필터의 빠른 개요

Ball 필터는 푸시 소스입니다. 즉, 끌어오기 원본이 아니라 작업자 스레드를 사용하여 샘플을 다운스트림으로 전달하며, 이는 다운스트림 필터가 샘플을 요청할 때까지 수동적으로 대기합니다. Ball 필터는 CSource 클래스에서 빌드되고 출력 핀은 CSourceStream 클래스에서 빌드됩니다. CSourceStream 클래스는 데이터 흐름을 구동하는 작업자 스레드를 만듭니다. 이 스레드는 할당자에서 샘플을 가져오고, 데이터로 채우고, 다운스트림으로 제공하는 루프를 입력합니다.

CSourceStream의 대부분의 작업은 파생 클래스가 구현하는 CSourceStream::FillBuffer 메서드에서 발생합니다. 이 메서드에 대한 인수는 전달할 샘플에 대한 포인터입니다. Ball 필터의 FillBuffer 구현은 샘플 버퍼의 주소를 검색하고 개별 픽셀 값을 설정하여 버퍼에 직접 그립니다. (그리기는 도우미 클래스인 CBall을 통해 수행되므로 비트 깊이, 색상표 등에 대한 세부 정보를 무시할 수 있습니다.)

검색을 위해 볼 필터 수정

Ball 필터를 검색할 수 있도록 하려면 하나의 출력 핀이 있는 필터에서 검색을 구현하도록 설계된 CSourceSeeking 클래스를 사용합니다. CSourceSeeking 클래스를 CBallStream 클래스의 상속 목록에 추가합니다.

class CBallStream :  // Defines the output pin.
    public CSourceStream, public CSourceSeeking

또한 CSourceSeeking 에 대한 이니셜라이저를 CBallStream 생성자에 추가해야 합니다.

    CSourceSeeking(NAME("SeekBall"), (IPin*)this, phr, &m_cSharedState),

이 문은 CSourceSeeking에 대한 기본 생성자를 호출합니다. 매개 변수는 이름, 소유 핀에 대한 포인터, HRESULT 값 및 중요한 섹션 개체의 주소입니다. 이름은 디버깅에만 사용되며 NAME 매크로는 소매 빌드의 빈 문자열로 컴파일됩니다. 핀은 CSourceSeeking을 직접 상속하므로 두 번째 매개 변수는 자체에 대한 포인터이며 IPin 포인터로 캐스팅됩니다. HRESULT 값은 기본 클래스의 현재 버전에서 무시됩니다. 이전 버전과의 호환성을 위해 유지합니다. 중요 섹션은 현재 시작 시간, 중지 시간 및 재생 속도와 같은 공유 데이터를 보호합니다.

CSourceSeeking 생성자 내에 다음 문을 추가합니다.

m_rtStop = 60 * UNITS;

m_rtStop 변수는 중지 시간을 지정합니다. 기본적으로 값은 약 14,600년인 _I64_MAX/2입니다. 이전 문은 더 보수적인 60초로 설정합니다.

CBallStream에 두 개의 추가 멤버 변수를 추가해야 합니다.

BOOL            m_bDiscontinuity; // If true, set the discontinuity flag.
REFERENCE_TIME  m_rtBallPosition; // Position of the ball. 

모든 seek 명령 후에 필터는 IMediaSample::SetDiscontinuity를 호출하여 다음 샘플에서 불연속성 플래그를 설정해야 합니다. m_bDiscontinuity 변수는 이를 추적합니다. m_rtBallPosition 변수는 비디오 프레임 내에서 볼의 위치를 지정합니다. 원래 볼 필터는 스트림 시간의 위치를 계산하지만 각 seek 명령 후에 스트림 시간이 0으로 다시 설정됩니다. 검색 가능한 스트림에서 절대 위치는 스트림 시간과 독립적입니다.

QueryInterface

CSourceSeeking 클래스는 IMediaSeeking 인터페이스를 구현합니다. 이 인터페이스를 클라이언트에 노출하려면 NonDelegatingQueryInterface 메서드를 재정의합니다.

STDMETHODIMP CBallStream::NonDelegatingQueryInterface
    (REFIID riid, void **ppv)
{
    if( riid == IID_IMediaSeeking ) 
    {
        return CSourceSeeking::NonDelegatingQueryInterface( riid, ppv );
    }
    return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}

이 메서드는 DirectShow 기본 클래스가 COM(구성 요소 개체 모델) 집계를 지원하는 방식 때문에 "NonDelegating"이라고 합니다. 자세한 내용은 DirectShow SDK의 "IUnknown을 구현하는 방법" 항목을 참조하세요.

검색 방법

CSourceSeeking 클래스는 검색과 관련된 여러 멤버 변수를 유지 관리합니다.

변수 Description 기본값
m_rtStart 시작 시간 0
m_rtStop 중지 시간 _I64_MAX /2
m_dRateSeeking 재생 속도 1.0

 

IMediaSeeking::SetPositionsCSourceSeeking 구현은 시작 및 중지 시간을 업데이트한 다음 파생 클래스인 CSourceSeeking::ChangeStart 및 CSourceSeeking::ChangeStop에서 두 개의 순수 가상 메서드를 호출합니다. IMediaSeeking::SetRate의 구현은 유사합니다. 재생 속도를 업데이트한 다음 순수 가상 메서드 CSourceSeeking::ChangeRate를 호출합니다. 이러한 각 가상 메서드에서 핀은 다음을 수행해야 합니다.

  1. IPin::BeginFlush를 호출하여 데이터 플러시를 시작합니다.
  2. 스트리밍 스레드를 중지합니다.
  3. IPin::EndFlush를 호출합니다.
  4. 스트리밍 스레드를 다시 시작합니다.
  5. IPin::NewSegment를 호출합니다.
  6. 다음 샘플에서 불연속성 플래그를 설정합니다.

스트리밍 스레드가 샘플을 제공하거나 새 샘플을 가져오기 위해 기다리는 동안 차단할 수 있으므로 이러한 단계의 순서는 매우 중요합니다. BeginFlush 메서드는 스트리밍 스레드가 차단되지 않으므로 2단계에서 교착 상태가 되지 않도록 합니다. EndFlush 호출은 새 샘플을 예상하도록 다운스트림 필터에 알리므로 스레드가 4단계에서 다시 시작될 때 거부하지 않습니다.

다음 프라이빗 메서드는 1~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();
    }
}

스트리밍 스레드가 다시 시작되면 CSourceStream::OnThreadStartPlay 메서드를 호출합니다. 이 메서드를 재정의하여 5단계와 6단계를 수행합니다.

HRESULT CBallStream::OnThreadStartPlay()
{
    m_bDiscontinuity = TRUE;
    return DeliverNewSegment(m_rtStart, m_rtStop, m_dRateSeeking);
}

ChangeStart 메서드에서 스트림 시간을 0으로 설정하고 공의 위치를 새 시작 시간으로 설정합니다. 그런 다음, CBallStream::UpdateFromSeek을 호출합니다.

HRESULT CBallStream::ChangeStart( )
{
    {
        CAutoLock lock(CSourceSeeking::m_pLock);
        m_rtSampleTime = 0;
        m_rtBallPosition = m_rtStart;
    }
    UpdateFromSeek();
    return S_OK;
}

ChangeStop 메서드에서 새 중지 시간이 현재 위치보다 작은 경우 CBallStream::UpdateFromSeek을 호출합니다. 그렇지 않으면 중지 시간은 아직 미래이므로 그래프를 플러시할 필요가 없습니다.

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;
}

속도 변경의 경우 CSourceSeeking::SetRate 메서드는 ChangeRate를 호출하기 전에 m_dRateSeeking 새 속도(이전 값 삭제)로 설정합니다. 아쉽게도 호출자가 잘못된 속도(예: 0보다 작음)를 제공한 경우 ChangeRate 가 호출되는 시간이 너무 늦습니다. 한 가지 해결 방법은 SetRate를 재정의하고 유효한 요금을 검사 것입니다.

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; }

버퍼에 그리기

다음은 각 프레임에서 공을 그리는 루틴인 CSourceStream::FillBuffer의 수정된 버전입니다.

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;
}

이 버전과 원본의 주요 차이점은 다음과 같습니다.

  • 앞에서 설명한 대로 m_rtBallPosition 변수는 스트림 시간이 아닌 공의 위치를 설정하는 데 사용됩니다.
  • 중요 섹션을 유지한 후 메서드는 현재 위치가 중지 시간을 초과하는지 여부를 확인합니다. 이 경우 기본 클래스가 데이터 전송을 중지하고 스트림 종료 알림을 전달하도록 신호를 보내는 S_FALSE 반환합니다.
  • 타임스탬프는 현재 속도로 나뉩니다.
  • m_bDiscontinuityTRUE이면 메서드는 샘플에서 불연속성 플래그를 설정합니다.

또 다른 사소한 차이가 있습니다. 원래 버전은 정확히 하나의 버퍼를 사용하므로 스트리밍이 시작될 때 전체 버퍼를 한 번 0으로 채웁니다. 그 후, 그것은 단지 이전 위치에서 공을 지웁니다. 그러나 이 최적화는 볼 필터에 약간의 버그를 표시합니다. CBaseOutputPin::D ecideAllocator 메서드가 IMemInputPin::NotifyAllocator를 호출하면 읽기 전용 플래그를 FALSE로 설정합니다. 따라서 다운스트림 필터는 버퍼에 자유롭게 쓸 수 있습니다. 한 가지 해결 방법은 읽기 전용 플래그를 TRUE로 설정하도록 DecideAllocator를 재정의하는 것입니다. 그러나 간단히 하기 위해 새 버전은 최적화를 완전히 제거합니다. 대신 이 버전은 버퍼를 매번 0으로 채웁니다.

기타 변경 내용

새 버전에서는 이러한 두 줄이 CBall 생성자에서 제거됩니다.

    m_iRandX = rand();
    m_iRandY = rand();

원래 볼 필터는 이러한 값을 사용하여 초기 볼 위치에 임의성을 추가합니다. 우리의 목적을 위해, 우리는 공이 결정적이되기를 바랍니다. 또한 일부 변수가 CRefTime 개체에서 REFERENCE_TIME 변수로 변경되었습니다. CRefTime 클래스는 REFERENCE_TIME 값에 대한 씬 래퍼입니다. 마지막으로 IQualityControl::Notify의 구현이 약간 수정되었습니다. 자세한 내용은 소스 코드를 참조할 수 있습니다.

CSourceSeeking 클래스의 제한 사항

CSourceSeeking 클래스는 교차 핀 통신 문제로 인해 여러 출력 핀이 있는 필터에 대한 것이 아닙니다. 예를 들어 인터리브 오디오 비디오 스트림을 수신하고, 스트림을 오디오 및 비디오 구성 요소로 분할하고, 한 출력 핀과 오디오에서 다른 출력 핀에서 비디오를 제공하는 파서 필터를 생각해 보세요. 두 출력 핀 모두 모든 seek 명령을 수신하지만 필터는 seek 명령당 한 번만 검색해야 합니다. 해결 방법은 검색을 제어하고 다른 핀에서 받은 검색 명령을 무시할 핀 중 하나를 지정하는 것입니다.

그러나 seek 명령 후에는 두 핀이 모두 데이터를 플러시해야 합니다. 문제를 더 복잡하게 하기 위해 seek 명령은 스트리밍 스레드가 아닌 애플리케이션 스레드에서 발생합니다. 따라서 두 핀이 모두 차단되지 않았고 IMemInputPin::Receive 호출이 반환될 때까지 기다리거나 교착 상태가 발생할 수 있는지 확인해야 합니다. 핀의 스레드로부터 안전한 플러시에 대한 자세한 내용은 스레드 및 중요 섹션을 참조하세요.

원본 필터 작성