Örnek Olay İncelemesi: MPEG-1 Medya Kaynağı

Microsoft Media Foundation'da, veri işlem hattına medya verilerini tanıtır nesnesi, medya kaynağıolarak adlandırılır. Bu konu başlığında MPEG-1 Medya Kaynağı SDK Örneği ayrıntılı bir şekilde ele alınır.

Önkoşullar

Bu konuyu okumadan önce aşağıdaki Media Foundation kavramlarını anlamanız gerekir:

Media Foundation mimarisini, özellikle de işlem hattındaki medya kaynaklarının rolünü temel olarak anlamanız gerekir. (Daha fazla bilgi için bkz. Medya Kaynakları.)

Ayrıca, burada açıklanan adımlara daha genel bir genel bakış sağlayan Özel Medya Kaynağı yazma konusunu okumak isteyebilirsiniz.

Bu konu, örnek oldukça büyük olduğundan SDK örneğindeki kodun tamamını yeniden oluşturmaz.

MPEG-1 Kaynağında Kullanılan C++ Sınıfları

Örnek MPEG-1 kaynağı aşağıdaki C++ sınıflarıyla uygulanır:

  • MPEG1ByteStreamHandler. Medya kaynağı için bayt akışı işleyicisini uygular. Bayt akışı verüldüğünde, bayt akışı işleyicisi kaynağın bir örneğini oluşturur.
  • MPEG1Source. Medya kaynağını uygular.
  • MPEG1Stream. Medya akışı nesnelerini uygular. Medya kaynağı, MPEG-1 bit akışındaki her ses veya video akışı için bir MPEG1Stream nesnesi oluşturur.
  • Parser. MPEG-1 bit akışını ayrıştırıyor. Çoğunlukla, bu sınıfın ayrıntıları Media Foundation API'leriyle ilgili değildir.
  • SourceOp, OpQueue: Bu iki sınıf, medya kaynağındaki zaman uyumsuz işlemleri yönetir. (Bkz. Zaman Uyumsuz İşlemler).

Diğer çeşitli yardımcı sınıflar, konunun ilerleyen bölümlerinde açıklanmıştır.

Byte-Stream İşleyicisi

bayt akışı işleyicisi, medya kaynağını oluşturan nesnedir. Bayt akışı işleyicisi kaynak çözümleyici tarafından oluşturulur; uygulamalar bayt akışı işleyicisi ile doğrudan etkileşim kurmaz. Kaynak çözümleyici kayıt defterine bakarak bayt akışı işleyicisini bulur. İşleyiciler dosya adı uzantısına veya MIME türüne göre kaydedilir. MPEG-1 kaynağı için byte-stream işleyicisi ".mpg" dosya adı uzantısı için kaydedilir.

Not

Özel URL düzenlerini desteklemek istiyorsanız, birdüzeni işleyicisi de yazabilirsiniz. MPEG-1 kaynağı yerel dosyalar için tasarlanmıştır ve Media Foundation zaten "file://" URL'ler için bir düzen işleyicisi sağlar.

 

Bayt akışı işleyicisi, IMFByteStreamHandler arabirimini uygular. Bu arabirimin uygulanması gereken en önemli iki yöntemi vardır:

  • BeginCreateObject. Medya kaynağını oluşturmak için zaman uyumsuz bir işlem başlatır.
  • EndCreateObject. Eşzamanlı olmayan çağrıyı tamamlıyor.

diğer iki yöntem isteğe bağlıdır ve SDK örneğinde uygulanmaz:

  • cancelObjectCreation. BeginCreateObject yöntemini iptal eder. Bu yöntem, başlangıçta yüksek gecikme süresine sahip olabilecek bir ağ kaynağı için kullanışlıdır.
  • GetMaxNumberOfBytesRequiredForResolution. İşleyicinin kaynak akıştan okuyacağı bayt sayısı üst sınırını alır. Bayt akışı işleyicisinin medya kaynağını oluşturmadan önce ne kadar veri olduğunu biliyorsanız bu yöntemi uygulayın. Aksi takdirde, E_NOTIMPLdöndürmeniz yeterlidir.

BeginCreateObject yönteminin uygulaması aşağıdadır:

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

yöntemi aşağıdaki adımları gerçekleştirir:

  1. MPEG1Source nesnesinin yeni bir örneğini oluşturur.
  2. Zaman uyumsuz bir sonuç nesnesi oluşturun. Bu nesne daha sonra kaynak çözümleyicinin geri çağırma yöntemini çağırmak için kullanılır.
  3. MPEG1Source sınıfında tanımlanan zaman uyumsuz bir yöntem olan MPEG1Source::BeginOpençağırır.
  4. ppIUnknownCancelCookieNULLolarak ayarlar. Bu, çağıranı CancelObjectCreation'nin desteklenmediğini bildirir.

MPEG1Source::BeginOpen yöntemi bayt akışını okumak ve MPEG1Source nesnesini başlatmak için gerçek işi yapar. Bu yöntem genel API'nin bir parçası değildir. İşleyici ile medya kaynağı arasında gereksinimlerinize uygun herhangi bir mekanizma tanımlayabilirsiniz. Mantığın çoğunu medya kaynağına yerleştirmek bayt akışı işleyicisini nispeten basit tutar.

Kısaca BeginOpen şunları yapar:

  1. Kaynak bayt akışının hem okunabilir hem de aranabilir olduğunu doğrulamak için IMFByteStream::GetCapabilities çağırır.
  2. Asenkron bir G/Ç isteği başlatmak için IMFByteStream::BeginRead çağırır.

Başlatma işleminin geri kalanı zaman uyumsuz olarak gerçekleşir. Medya kaynağı, MPEG-1 dizi başlıklarını ayrıştırmak için akıştan yeterince veri okur. Ardından, dosyadaki ses ve video akışlarını açıklamak için kullanılan nesne olan sunu tanımlayıcısı oluşturur. (Daha fazla bilgi için bkz. Sunu Tanımlayıcısı.) BeginOpen işlemi tamamlandığında bayt akışı işleyicisi kaynak çözümleyicinin geri çağırma yöntemini çağırır. Bu noktada, kaynak çözümleyici IMFByteStreamHandler::EndCreateObjectçağırır. EndCreateObject yöntemi işlemin durumunu döndürür.

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

Bu işlem sırasında herhangi bir zamanda bir hata oluşursa, geri çağırma bir hata durum koduyla çağrılır.

Sunu Tanımlayıcısı

Sunu tanımlayıcısı, AŞAĞıDAKI bilgiler de dahil olmak üzere MPEG-1 dosyasının içeriğini açıklar:

  • Akışların sayısı.
  • Her akışın biçimi.
  • Akış tanımlayıcıları.
  • Her akışın seçim durumu (seçili veya seçimi kaldırılmış).

Media Foundation mimarisi açısından, sunum tanımlayıcısı bir veya daha fazla akış tanımlayıcısıiçerir. Her akış tanımlayıcısı, akışta medya türlerini almak veya ayarlamak için kullanılan birmedya türü işleyicisi içerir. Media Foundation, sunu tanımlayıcısı ve akış tanımlayıcısı için stok uygulamaları sağlar; bunlar çoğu medya kaynağı için uygundur.

Sunu tanımlayıcısı oluşturmak için aşağıdaki adımları uygulayın:

  1. Her akış için:
    1. Bir akış kimliği ve olası medya türleri dizisi sağlayın. Akış birden fazla medya türünü destekliyorsa, varsa medya türleri listesini tercihe göre sıralar. (En uygun türü önce, en az uygun türü en sona yazın.)
    2. Akış tanımlayıcısını oluşturmak için MFCreateStreamDescriptorçağırın.
    3. Yeni oluşturulan akış tanımlayıcısı üzerinde IMFStreamDescriptor::GetMediaTypeHandler çağırın.
    4. Akışın varsayılan biçimini ayarlamak için IMFMediaTypeHandler::SetCurrentMediaType çağırın. Birden fazla medya türü varsa, genellikle listedeki ilk türü ayarlamanız gerekir.
  2. MFCreatePresentationDescriptor çağırın ve akış tanımlayıcı işaretçileri dizisini iletin.
  3. Her akış için, varsayılan seçim durumunu ayarlamak için IMFPresentationDescriptor::SelectStream veya DeselectStreamçağrısı yapın. Aynı türde birden fazla akış varsa (ses veya video), varsayılan olarak yalnızca bir akış seçilmelidir.

MPEG1Source nesnesi, InitPresentationDescriptor yönteminde sunu tanımlayıcısını oluşturur:

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

Uygulama, IMFMediaSource::CreatePresentationDescriptorçağırarak sunu tanımlayıcısını alır. Bu yöntem, IMFPresentationDescriptor::Cloneçağrısı yaparak sunu tanımlayıcısının sığ bir kopyasını oluşturur. (Kopya özgün akış tanımlayıcılarının işaretçilerini içerir.) Uygulama, medya türünü ayarlamak, bir akış seçmek veya bir akışın seçimini kaldırmak için sunu tanımlayıcısını kullanabilir.

İsteğe bağlı olarak, sunu tanımlayıcıları ve akış tanımlayıcıları, kaynak hakkında ek bilgi veren öznitelikler içerebilir. Bu tür özniteliklerin listesi için aşağıdaki konulara bakın:

Bir öznitelik özel bahsetmeyi hak ediyor: MF_PD_DURATION özniteliği kaynağın toplam süresini içerir. Süreyi biliyorsanız bu özniteliği ayarlayın; örneğin, süre dosya biçimine bağlı olarak dosya üst bilgilerinde belirtilebilir. Uygulama bu değeri görüntüleyebilir veya ilerleme çubuğu veya arama çubuğu ayarlamak için kullanabilir.

Akış Durumları

Medya kaynağı aşağıdaki durumları tanımlar:

Devlet Açıklama
Başladı Kaynak, örnek istekleri kabul eder ve işler.
Duraklatıldı Kaynak örnek istekleri kabul eder, ancak işlemez. Talepler, kaynak başlatılana kadar sıraya alınır.
Durdu. Kaynak örnek istekleri reddeder.

 

Başlamak

IMFMediaSource::Start yöntemi medya kaynağını başlatır. Aşağıdaki parametreleri alır:

  • Sunu tanımlayıcısı.
  • Zaman biçimi GUID'i.
  • Başlangıç konumu.

Uygulamanın, kaynakta CreatePresentationDescriptor çağırarak sunu tanımlayıcısını alması gerekir. Sunu tanımlayıcısını doğrulamak için tanımlı bir mekanizma yoktur. Uygulama yanlış sunu tanımlayıcısını belirtirse, sonuçlar tanımlanmamış olur.

Zaman biçimi GUID'i, başlangıç konumunun nasıl yorum yapılacağını belirtir. Standart biçim, GUID_NULL tarafından gösterilen 100 nanosaniyelik (ns) birimdir. Her medya kaynağı 100 ns birimi desteklemelidir. İsteğe bağlı olarak, bir kaynak çerçeve numarası veya zaman kodu gibi diğer zaman birimlerini destekleyebilir. Ancak, bir medya kaynağını desteklediği zaman biçimlerinin listesi için sorgulamanın standart bir yolu yoktur.

Başlangıç konumu, saat biçimine bağlı olarak farklı veri türlerine izin veren PROPVARIANTolarak verilir. 100 ns için PROPVARIANT türü, VT_I8 veya VT_EMPTY. VT_I8ise, PROPVARIANT 100 ns birim cinsinden başlangıç konumunu içerir. VT_EMPTY değerinin "geçerli konumdan başlayın" özel anlamı vardır.

Start yöntemini aşağıdaki gibi uygulayın:

  1. Parametreleri ve durumu doğrulayın:
    • NULL parametrelerini denetleyin.
    • Zaman biçimi GUID'sini denetleyin. Değer geçersizse, MF_E_UNSUPPORTED_TIME_FORMATdöndürür.
    • Başlangıç konumunu tutan PROPVARIANT veri türünü denetleyin.
    • Başlangıç konumunu doğrulayın. Geçersizse, MF_E_INVALIDREQUESTdeğerini döndürün.
    • Kaynak kapatıldıysa, MF_E_SHUTDOWNdöndürin.
  2. 1. adımda hata oluşmazsa, asenkron bir işlemi sıraya ekleyin. Bu adımdan sonraki her şey iş kuyruğu dizisinde gerçekleşir.
  3. Her akış için:
    1. Önceki bir Başlat isteğinden akışın zaten etkin olup olmadığını kontrol edin.

    2. Akışın uygulama tarafından seçilip seçilmediğini veya seçimini kaldırıp kaldırmadığını kontrol etmek için IMFPresentationDescriptor::GetStreamDescriptorByIndex çağrısında bulunun.

    3. Daha önce seçilen bir akışın seçimi kaldırıldığında, bu akış için teslim edilmemiş tüm örnekleri boşaltın.

    4. Eğer akış aktifse, medya kaynağı (akış değil) aşağıdaki olaylardan birini gönderir:

      • Akışın daha önce etkin olup olmadığını meUpdatedStream gönderir.
      • Aksi takdirde, MENewStreamgönderir.

      Her iki olay için de olay verileri, akışın IMFMediaStream işaretçisidir.

    5. Kaynak duraklatılmış durumdan yeniden başlatılıyorsa bekleyen örnek istekler olabilir. Öyleyse, bunları şimdi teslim edin.

    6. Kaynak yeni bir konum arayışındaysa, her akış nesnesi bir MEStreamSeeked olayı gönderir. Aksi takdirde, her akış bir MEStreamStarted olayı gönderir.

  4. Kaynak yeni bir konum arıyorsa, medya kaynağı bir MESourceSeeked olayı gönderir. Aksi takdirde, bir MESourceStarted etkinliği gönderir.

2. adımdan sonra herhangi bir zamanda hata oluşursa, kaynak bir hata koduyla MESourceStarted olayı gönderir. Bu, uygulamayı Başlat yönteminin zaman uyumsuz olarak başarısız olduğu konusunda uyarır.

Aşağıdaki kod 1-2 adımlarını gösterir:

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

Kalan adımlar sonraki örnekte gösterilmiştir:

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

Durdur

IMFMediaSource::Pause yöntemi medya kaynağını duraklatır. Bu yöntemi aşağıdaki gibi uygulayın:

  1. Eşzamansız bir işlemi kuyruğa alın.
  2. Her etkin akış bir MEStreamPaused olayı gönderir.
  3. Medya kaynağı bir MESourcePaused etkinliği gönderir.

Duraklatılmış olsa da kaynak, örnek istekleri işlemeden kuyruğa alır. (Bkz. Örnek İstekler.)

Durmak

IMFMediaSource::Stop yöntemi medya kaynağını durdurur. Bu yöntemi aşağıdaki gibi uygulayın:

  1. Eşzamansız bir işlemi kuyruğa alın.
  2. Her etkin akış bir MEStreamStopped olayı gönderir.
  3. Kuyruğa alınan tüm örnekleri ve örnek istekleri temizleyin.
  4. Medya kaynağı bir MESourceStopped etkinliği gönderir.

Durdurulurken, kaynak tüm örnek isteklerini reddeder.

G/Ç isteği devam ederken kaynak durdurulursa, kaynak durduruldu durumuna girdikten sonra G/Ç isteği tamamlanabilir. Bu durumda, kaynak bu G/Ç isteğinin sonucunu atmalıdır.

Örnek İstekler

Media Foundation, işlem hattının medya kaynağından örnekler talep ettiği bir pull modeli kullanır. Bu, kaynakların örnekleri "gönderdiği" DirectShow tarafından kullanılan modelden farklıdır.

Media Foundation işlem hattı, yeni bir örnek istemek için IMFMediaStream::RequestSampleçağırır. Bu yöntem, bir belirteci nesnesini temsil eden bir IUnknown işaretçisi alır. Belirteç nesnesinin uygulanması çağıranın inisiyatifindedir; bu, yalnızca çağıranın örnek istekleri takip etmesi için bir yol sağlar. Belirteç parametresi NULLde olabilir.

Kaynağın verileri okumak için zaman uyumsuz G/Ç istekleri kullandığı varsayıldığında, örnek oluşturma örnek isteklerle eşitlenmez. Örnek istekleri örnek oluşturma ile eşitlemek için bir medya kaynağı aşağıdakileri yapar:

  1. İstek belirteçleri kuyruğa yerleştirilir.
  2. Örnekler oluşturuldukça ikinci bir kuyruğa yerleştirilir.
  3. Medya kaynağı, ilk kuyruktan bir istek belirteci ve ikinci kuyruktan bir örnek çekerek örnek isteğini tamamlar.
  4. Medya kaynağı bir MEMediaSample olayı gönderir. Olay, örneğe ilişkin bir işaretçi içerir ve örnek de belirtecin işaretçisini içerir.

Aşağıdaki diyagramda MEMediaSample olayı, örnek ve istek belirteci arasındaki ilişki gösterilmektedir.

mediasample ve imfsample'a işaret eden bir örnek kuyruğunu gösteren diyagram; imfsample ve istek kuyruğu iunknown'a işaret ediyor

Örnek MPEG-1 kaynağı aşağıdaki gibi bu işlemi uygular:

  1. RequestSample yöntemi isteği bir FIFO kuyruğuna yerleştirir.
  2. G/Ç istekleri tamamlandıkçe, medya kaynağı yeni örnekler oluşturur ve bunları ikinci bir FIFO kuyruğuna yerleştirir. (Bu kuyruğun maksimum boyutu, kaynağın çok ileriye okumasını önler.)
  3. Bu kuyrukların her ikisinde de en az bir öğe (bir istek ve bir örnek) olduğunda, medya kaynağı örnek kuyruktan ilk örneği göndererek istek kuyruğundan ilk isteği tamamlar.
  4. Örnek teslim etmek için akış nesnesi (kaynak nesne değil) bir MEMediaSample olayı gönderir.
    • Olay verileri, örneğin IMFSample arabirimine yönelik bir işaretçidir.
    • İstek bir belirteç içeriyorsa, örnekteki MFSampleExtension_Token özniteliğini ayarlayarak belirteci örneğe ekleyin.

Bu noktada üç olasılık vardır:

  • Örnek kuyrukta başka bir örnek var, ancak eşleşen istek yok.
  • bir istek var, ancak örnek yok.
  • Her iki kuyruk da boş; örnek ve istek yok.

Örnek kuyruk boşsa, kaynak akışın sonunu denetler (bkz. Akış Sonu). Aksi takdirde, veriler için başka bir G/Ç isteği oluşturur. Bu işlem sırasında herhangi bir hata oluşursa akış bir MEError olayı gönderir.

Aşağıdaki kod, IMFMediaStream::RequestSample yöntemini uygular:

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

DispatchSamples yöntemi, örnekleri örnek kuyruğundan çeker, bunları bekleyen örnek istekleriyle eşleştirir ve MEMediaSample olaylarını kuyruğa alır.

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

DispatchSamples yöntemi aşağıdaki durumlarda çağrılır:

  • RequestSample yönteminin içinde.
  • Medya kaynağı duraklatılmış durumdan yeniden başlatıldığında.
  • G/Ç isteği tamamlandığında.

Akış Sonu

Bir akışta daha fazla veri olmadığında ve bu akış için tüm örnekler teslim edildiğinde, akış nesneleri bir MEEndOfStream olayı gönderir.

Tüm etkin akışlar tamamlandığında, medya kaynağı bir MEEndOfPresentation olayı gönderir.

Zaman Uyumsuz İşlemler

Medya kaynağı yazmanın belki de en zor kısmı Media Foundation zaman uyumsuz modelini anlamaktır.

Bir medya kaynağında akışı denetleen tüm yöntemler zaman uyumsuzdur. Her durumda yöntemi, parametreleri denetleme gibi bazı ilk doğrulamalar yapar. Kaynak daha sonra işin geri kalanını bir iş kuyruğuna gönderir. İşlem tamamlandıktan sonra medya kaynağı, medya kaynağının IMFMediaEventGenerator arabirimi aracılığıyla çağırana bir olay gönderir. Bu nedenle iş kuyruklarını anlamak önemlidir.

Bir öğeyi iş kuyruğuna yerleştirmek için MFPutWorkItem veya MFPutWorkItemExçağırabilirsiniz. MPEG-1 kaynağı MFPutWorkItemkullanır, ancak iki işlev aynı şeyi yapar. MFPutWorkItem işlevi aşağıdaki parametreleri alır:

  • İş kuyruğunu tanımlayan DWORD değeri. Özel bir iş kuyruğu oluşturabilir veya MFASYNC_CALLBACK_QUEUE_STANDARDkullanabilirsiniz.
  • IMFAsyncCallback arabirimine bir işaretçi. Bu geri çağırma arabirimi, işi gerçekleştirmek için çağrılır.
  • IUnknownuygulaması gereken isteğe bağlı bir durum nesnesi.

Bir veya daha fazla çalışan iş parçacığı, iş kuyruğuna hizmet eder ve sürekli olarak kuyruktan bir sonraki iş öğesini çeker, ardından geri çağırma arabiriminin IMFAsyncCallback::Invoke yöntemini çağırır.

İş öğelerinin, onları kuyruğa eklediğiniz sırayla yürütülmesi garanti değildir. Birden fazla iş parçacığının aynı iş kuyruğuna hizmet verebileceğini unutmayın; bu nedenle Invoke çağrıları çakışabilir veya sıralı olmayabilir. Bu nedenle, medya kaynağının, iş kuyruğu öğelerini doğru sırada sunarak iç durumu doğru şekilde koruması gerekir. Yalnızca önceki işlem tamamlandığında kaynak sonraki işlemi başlatır.

BEKLEYEN işlemleri temsil etmek için MPEG-1 kaynağı SourceOpadlı bir sınıf tanımlar:

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

Operation numaralandırması hangi işlemin beklemede olduğunu belirler. Sınıf ayrıca, işlem için ek verileri iletmek üzere bir PROPVARIANT içerir.

İşlem Kuyruğu

İşlemleri seri hale getirmek için, medya kaynağı bir SourceOp nesnesi kuyruğu tutar. Kuyruğu yönetmek için bir yardımcı sınıfı kullanır:

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

OpQueue sınıfı, zaman uyumsuz iş öğeleri gerçekleştiren bileşen tarafından devralınacak şekilde tasarlanmıştır. OP_TYPE şablon parametresi, kuyruktaki iş öğelerini temsil etmek için kullanılan nesne türüdür; bu durumda OP_TYPESourceOpolur. OpQueue sınıfı aşağıdaki yöntemleri uygular:

  • QueueOperation kuyruğa yeni bir öğe ekler.
  • ProcessQueue kuyruktan sonraki işlemi gönderir. Bu yöntem zaman uyumsuzdur.
  • ProcessQueueAsync zaman uyumsuz ProcessQueue yöntemini tamamlar.

Türetilmiş sınıf tarafından başka iki yöntem uygulanmalıdır:

  • ValidateOperation, medya kaynağının geçerli durumu göz önünde bulundurularak belirtilen işlemi gerçekleştirmenin geçerli olup olmadığını denetler.
  • DispatchOperation eşzamansız iş görevini gerçekleştirir.

İşlem kuyruğu aşağıdaki gibi kullanılır:

  1. Media Foundation işlem hattı, medya kaynağında IMFMediaSource::Startgibi zaman uyumsuz bir yöntem çağırır.
  2. Zaman uyumsuz yöntem, Start işlemini kuyruğa koyan ve ProcessQueue'i (SourceOp nesnesi biçiminde) çağıran QueueOperation'ı çağırır.
  3. ProcessQueue MFPutWorkItemçağırır.
  4. İş kuyruğu iş parçacığı ProcessQueueAsync'ı çağırır.
  5. ProcessQueueAsync yöntemi ValidateOperation ve DispatchOperationçağırır.

Aşağıdaki kod, MPEG-1 kaynağında yeni bir işlem kuyruğa alır:

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

Aşağıdaki kod kuyruğu işler:

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

ValidateOperation yöntemi, MPEG-1 kaynağının kuyruktaki bir sonraki işlemi gönderip gönderemeyeceğini denetler. Devam eden başka bir işlem varsa, ValidateOperationMF_E_NOTACCEPTING'i döndürür. Bu, bekleyen başka bir işlem varken DispatchOperation çağrılmamasını sağlar.

HRESULT MPEG1Source::ValidateOperation(SourceOp *pOp)
{
    if (m_pCurrentOp != NULL)
    {
        return MF_E_NOTACCEPTING;
    }
    return S_OK;
}

DispatchOperation yöntemi işlem türünü değiştirir:

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

Özetlemek gerekirse:

  1. İşlem hattı, IMFMediaSource::Startgibi zaman uyumsuz bir yöntemi çağırır.
  2. Zaman uyumsuz yöntem, SourceOp nesnesine bir işaretçi geçirerek OpQueue::QueueOperationçağırır.
  3. QueueOperation yöntemi, işlemi m_OpQueue kuyruğuna yerleştirir ve OpQueue::ProcessQueueçağırır.
  4. ProcessQueue yöntemi MFPutWorkItemçağırır. Bu noktadan itibaren her şey Media Foundation iş kuyruğu iş parçacığında gerçekleşir. Asenkron yöntem, çağırana geri döner.
  5. İş kuyruğu iş parçacığı OpQueue::ProcessQueueAsync yöntemini çağırır.
  6. ProcessQueueAsync yöntemi, işlemi doğrulamak için MPEG1Source:ValidateOperation çağırır.
  7. ProcessQueueAsync yöntemi, işlemi işlemek için MPEG1Source::DispatchOperation çağırır.

Bu tasarımın çeşitli avantajları vardır:

  • Yöntemler zaman uyumsuz olduğundan, çağıran uygulamanın iş parçacığını bloke etmez.
  • İşlemler, işlem hattı bileşenleri arasında paylaşılan bir Media Foundation iş kuyruğu iş parçacığı üzerinden yürütülür. Bu nedenle, medya kaynağı kendi iş parçacığını oluşturmaz, böylece oluşturulabilecek toplam iş parçacığı sayısını azaltır.
  • Medya kaynağı işlemlerin tamamlanmasını beklerken engellemez. Bu, bir medya kaynağının yanlışlıkla kilitlenmeye neden olma olasılığını azaltır ve bağlam geçişini azaltmaya yardımcı olur.
  • Medya kaynağı, kaynak dosyayı okumak için zaman uyumsuz G/Ç'yi, (IMFByteStream::BeginReadçağrısı yaparak) kullanabilir. G/Ç işleminin tamamlanmasını beklerken medya kaynağının beklemede kalması gerekmez.

SDK örneğinde gösterilen deseni izlerseniz, medya kaynağınızın belirli ayrıntılarına odaklanabilirsiniz.

Medya Kaynakları

Özel Medya Kaynağı Yazma