如何使用Microsoft Media Foundation的Sink Writer播放音频

时间:2019-02-27 21:35:11

标签: c++ audio com playback ms-media-foundation

我正在尝试使用Microsoft Media Foundation创建一个音频可视化器。为此,我需要截取样本并同时播放它们。使用具有拓扑结构的媒体会话和样本收集器接收器似乎不切实际且过于复杂,因此,我为此尝试使用接收器读取器和接收器写入器的组合(请参阅{{3}上图像的右半部分) }。不幸的是,Overview of the Media Foundation Architecture并未真正解释如何执行此操作。 《开发Microsoft Media Foundation应用程序》一书在第92页上包含了从源到接收的循环,但这仍然对我没有帮助。

创建Source Reader可以正常工作,而且我正在读取非零样本。将它们写入Sink Writer(使用Streaming Audio Renderer)不会给我任何错误,但是我什么也听不到。我尝试了多种选择,例如选择其他媒体类型并显式选择渲染设备(尽管我只显示一种),但无济于事。请注意,不过使用Media Session播放音频效果很好!

我的代码基于以下问题:Audio/Video Playback

这是我目前的代码:

#include <iostream>

#include <cassert>

#include <mfidl.h>
#include <mfapi.h>
#include <mfreadwrite.h>
#include <Mferror.h>
#pragma comment(lib, "mf")
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mfreadwrite")

#include <winrt/base.h>
#pragma comment(lib, "windowsapp")

void winHr(const HRESULT result) { winrt::check_hresult(result); }

template<class T>
struct ComPtr : winrt::com_ptr<T>
{
    auto operator&() noexcept { return this->put(); }

    operator T*() noexcept
    {
        assert(this->get());
        return this->get();
    }
};

int main() noexcept
{
    winHr(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
    winHr(MFStartup(MF_VERSION));

    {
        ComPtr<IMFSourceReader> reader;
        winHr(MFCreateSourceReaderFromURL(
            LR"(test.wav)",
            nullptr, &reader));

        constexpr auto inStreamIndex = MF_SOURCE_READER_FIRST_AUDIO_STREAM;

        // Select only the audio stream
        winHr(reader->SetStreamSelection(MF_SOURCE_READER_ALL_STREAMS, false));
        winHr(reader->SetStreamSelection(inStreamIndex, true));

        ComPtr<IMFMediaSink> mediaSink;
        winHr(MFCreateAudioRenderer(nullptr, &mediaSink));

        ComPtr<IMFSinkWriter> writer;

        {
            ComPtr<IMFStreamSink> streamSink;
            winHr(mediaSink->GetStreamSinkByIndex(0, &streamSink));

            ComPtr<IMFMediaTypeHandler> typeHandler;
            winHr(streamSink->GetMediaTypeHandler(&typeHandler));

            ComPtr<IMFMediaType> inputType;
            winHr(reader->GetCurrentMediaType(inStreamIndex, &inputType));

            ComPtr<IMFMediaType> closestSupportedType;
            const auto result = typeHandler->IsMediaTypeSupported(inputType, &closestSupportedType);
            if (result == MF_E_INVALIDMEDIATYPE)
            {
                if (!closestSupportedType)
                {
                    std::cerr << "Media type not supported" << std::endl;
                    winHr(mediaSink->Shutdown());
                    goto end; //:o
                }
                winHr(reader->SetCurrentMediaType(inStreamIndex, nullptr, closestSupportedType));
                winHr(typeHandler->SetCurrentMediaType(closestSupportedType));
                winHr(MFCreateSinkWriterFromMediaSink(mediaSink, nullptr, &writer));
                winHr(writer->SetInputMediaType(0, closestSupportedType, nullptr));
            }
            else {
                winHr(result);
                winHr(reader->SetCurrentMediaType(inStreamIndex, nullptr, inputType));
                winHr(typeHandler->SetCurrentMediaType(inputType));
                winHr(MFCreateSinkWriterFromMediaSink(mediaSink, nullptr, &writer));
                winHr(writer->SetInputMediaType(0, inputType, nullptr));
            }
        }

        winHr(writer->BeginWriting());
        while (true)
        {
            ComPtr<IMFSample> sample;
            DWORD streamFlags;
            MFTIME timestamp;
            winHr(reader->ReadSample(inStreamIndex, 0, nullptr, &streamFlags, &timestamp, &sample));

            if (streamFlags & MF_SOURCE_READERF_ENDOFSTREAM)
            {
                winHr(writer->NotifyEndOfSegment(0));
                break;
            }
            if (streamFlags & MF_SOURCE_READERF_STREAMTICK)
                winHr(writer->SendStreamTick(0, timestamp));

            if (!sample) continue;

            winHr(sample->SetSampleTime(timestamp));
            winHr(writer->WriteSample(0, sample));
        }
        winHr(writer->Flush(0));

        std::cout << "(Press enter to stop)" << std::endl;
        std::cin.get();

        winHr(writer->Finalize());
        writer.attach(nullptr);
        winHr(mediaSink->Shutdown());
    }

end:
    winHr(MFShutdown());
    CoUninitialize();
}

请清楚一点:运行此命令时,它会打印(Press enter to stop),并且可以从耳机的噪音(读取:来自电子信号的失真)中听到,我可以在短时间内推断出音频端口已打开,然后关闭,但没有实际音频播放。我该如何使用它?

编辑1:我只是解决了如果result != MF_E_INVALIDMEDIATYPE我没有设置媒体类型的问题,但是现在我经常(但由于某些原因并不总是)得到MF_E_TOPO_CODEC_NOT_FOUND winHr(writer->SetInputMediaType(0, inputType, nullptr));。为什么会这样呢? (无论如何仍然不会播放音频。)

编辑2:显然,当我创建编写器时,它很重要,所以现在我只在最后一刻才这样做,但是现在出现“不支持媒体类型”错误。也许我需要手动选择某种媒体类型,但稍后我会进行研究-除非有人知道答案。

2 个答案:

答案 0 :(得分:0)

此代码对wav文件(Win7,默认声卡)对我来说很好用:

//----------------------------------------------------------------------------------------------
// Main.cpp
//----------------------------------------------------------------------------------------------
#pragma once
#define WIN32_LEAN_AND_MEAN
#define STRICT

#pragma comment(lib, "mfplat")
#pragma comment(lib, "mfreadwrite")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfuuid")

//----------------------------------------------------------------------------------------------
// Microsoft Windows SDK for Windows 7
#include <WinSDKVer.h>
#include <new>
#include <windows.h>
#include <strsafe.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mferror.h>
#include <mfreadwrite.h>

template <class T> inline void SAFE_RELEASE(T*& p){

    if(p){
        p->Release();
        p = NULL;
    }
}

#define AUDIO_FILE L"C:\\Project\\Media\\Audio\\test.wav"

HRESULT ProcessAudio(const WCHAR*);

void main(){

    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

    if(SUCCEEDED(hr)){

        hr = MFStartup(MF_VERSION, MFSTARTUP_LITE);

        if(SUCCEEDED(hr)){

            hr = ProcessAudio(AUDIO_FILE);

            hr = MFShutdown();
        }

        CoUninitialize();
    }
}

HRESULT ProcessAudio(const WCHAR*){

    IMFSourceReader* pSourceReader = NULL;
    IMFMediaType* pType = NULL;
    DWORD dwMediaTypeIndex = 0;
    IMFMediaSink* pAudioSink = NULL;
    IMFStreamSink* pStreamSink = NULL;
    IMFMediaTypeHandler* pMediaTypeHandler = NULL;
    IMFSinkWriter* pSinkWriter = NULL;

    HRESULT hr = MFCreateSourceReaderFromURL(AUDIO_FILE, NULL, &pSourceReader);

    // Check native media type
    if(SUCCEEDED(hr))
        hr = pSourceReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, dwMediaTypeIndex, &pType);

    SAFE_RELEASE(pType);

    // Get current media type
    if(SUCCEEDED(hr))
        hr = pSourceReader->GetCurrentMediaType(dwMediaTypeIndex, &pType);

    if(SUCCEEDED(hr))
        hr = MFCreateAudioRenderer(NULL, &pAudioSink);

    if(SUCCEEDED(hr))
        hr = pAudioSink->GetStreamSinkByIndex(0, &pStreamSink);

    if(SUCCEEDED(hr))
        hr = pStreamSink->GetMediaTypeHandler(&pMediaTypeHandler);

    if(FAILED(hr = pMediaTypeHandler->IsMediaTypeSupported(pType, NULL))){

        SAFE_RELEASE(pType);

        // This is a compatible type with my soundcard
        // MF_MT_MAJOR_TYPE                     MFMediaType_Audio
        // MF_MT_SUBTYPE                        MFAudioFormat_PCM
        // MF_MT_AUDIO_NUM_CHANNELS             2
        // MF_MT_AUDIO_SAMPLES_PER_SECOND       48000
        // MF_MT_AUDIO_BLOCK_ALIGNMENT          4
        // MF_MT_AUDIO_AVG_BYTES_PER_SECOND     192000
        // MF_MT_AUDIO_BITS_PER_SAMPLE          16
        // MF_MT_ALL_SAMPLES_INDEPENDENT        1
        // MF_MT_AUDIO_PREFER_WAVEFORMATEX      1

        hr = MFCreateMediaType(&pType);

        if(SUCCEEDED(hr))
            hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);

        if(SUCCEEDED(hr))
            hr = pType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);

        if(SUCCEEDED(hr))
            hr = pType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, 2);

        if(SUCCEEDED(hr))
            hr = pType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, 48000);

        if(SUCCEEDED(hr))
            hr = pType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 4);

        if(SUCCEEDED(hr))
            hr = pType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 192000);

        if(SUCCEEDED(hr))
            hr = pType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 16);

        if(SUCCEEDED(hr))
            hr = pType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);

        if(SUCCEEDED(hr))
            hr = pType->SetUINT32(MF_MT_AUDIO_PREFER_WAVEFORMATEX, TRUE);
    }

    if(SUCCEEDED(hr))
        hr = pMediaTypeHandler->SetCurrentMediaType(pType);

    if(SUCCEEDED(hr))
        hr = MFCreateSinkWriterFromMediaSink(pAudioSink, NULL, &pSinkWriter);

    if(SUCCEEDED(hr))
        hr = pSinkWriter->BeginWriting();

    BOOL bProcess = (hr == S_OK ? TRUE : FALSE);
    DWORD streamIndex;
    DWORD flags;
    LONGLONG llTimeStamp;
    IMFSample* pSample = NULL;

    while(bProcess){

        hr = pSourceReader->ReadSample((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, &streamIndex, &flags, &llTimeStamp, &pSample);

        if(SUCCEEDED(hr) && (flags == 0)){

            if(pSample){

                hr = pSinkWriter->WriteSample(0, pSample);

                SAFE_RELEASE(pSample);
            }
        }
        else{

            bProcess = FALSE;
        }
    }

    if(pSinkWriter)
        pSinkWriter->Finalize();

    if(pAudioSink)
        pAudioSink->Shutdown();

    SAFE_RELEASE(pSample);
    SAFE_RELEASE(pSinkWriter);
    SAFE_RELEASE(pMediaTypeHandler);
    SAFE_RELEASE(pStreamSink);
    SAFE_RELEASE(pAudioSink);
    SAFE_RELEASE(pType);
    SAFE_RELEASE(pSourceReader);

    return hr;
}

如果您遇到MF_E_TOPO_CODEC_NOT_FOUND的问题,请共享您的文件。在这种情况下,可能需要额外的代码。

编辑

  • 您有多个声卡吗?

  • 共享您的音频文件,以便我们了解发生了什么。

我对Sleep(40)的一点小技巧就是在这里告诉您,在不暂停的情况下执行“ while(true)”没有任何意义(您的处理器工作太多)。当然应该更好地决定40。 40是Windows OS上处理器的最短处理时间。我选择这个示例代码。我们可以讨论选择此值的最佳方法。我在等你的建议。

答案 1 :(得分:0)

我修改了您的代码,使其以下列方式工作: 1.枚举Audio Sink输出媒体类型,直到找到受支持的媒体类型。 2.将此媒体类型设置为Reader,以强制其使用Audio Resampler DSP(这是IMFMediaTopology所做的事情)。

这是代码,它可以正确播放输入的wav文件。请让我知道这对你有没有用。

#include <iostream>

#include <cassert>

#include <mfidl.h>
#include <mfapi.h>
#include <mfreadwrite.h>
#include <Mferror.h>
#pragma comment(lib, "mf")
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mfreadwrite")

#include <winrt/base.h>
#pragma comment(lib, "windowsapp")

void winHr(const HRESULT result) { winrt::check_hresult(result); }

template<class T>
struct ComPtr : winrt::com_ptr<T>
{
    auto operator&() noexcept { return this->put(); }

    operator T*() noexcept
    {
        assert(this->get());
        return this->get();
    }
};

int main() noexcept
{
    winHr(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
    winHr(MFStartup(MF_VERSION));

    {
        ComPtr<IMFSourceReader> reader;
        winHr(MFCreateSourceReaderFromURL(
            LR"(test.wav)",
            nullptr, &reader));

        constexpr auto inStreamIndex = MF_SOURCE_READER_FIRST_AUDIO_STREAM;

        // Select only the audio stream
        winHr(reader->SetStreamSelection(MF_SOURCE_READER_ALL_STREAMS, false));
        winHr(reader->SetStreamSelection(inStreamIndex, true));

        ComPtr<IMFMediaSink> mediaSink;
        winHr(MFCreateAudioRenderer(nullptr, &mediaSink));

        ComPtr<IMFSinkWriter> writer;

        {
            ComPtr<IMFStreamSink> streamSink;
            winHr(mediaSink->GetStreamSinkByIndex(0, &streamSink));

            ComPtr<IMFMediaTypeHandler> typeHandler;
            winHr(streamSink->GetMediaTypeHandler(&typeHandler));

            DWORD dwCount = 0;
            ComPtr<IMFMediaType> inputType;
            winHr(typeHandler->GetMediaTypeCount(&dwCount));

            for (INT i = 0; i < dwCount; i++)
            {
                inputType.attach(nullptr);
                winHr(typeHandler->GetMediaTypeByIndex(i, &inputType));
                if (SUCCEEDED(typeHandler->IsMediaTypeSupported(inputType, NULL)))
                    break;
            }

            //ComPtr<IMFMediaType> inputType;
            //winHr(reader->GetCurrentMediaType(inStreamIndex, &inputType));

            winHr(reader->SetCurrentMediaType(inStreamIndex, NULL, inputType));


            //ComPtr<IMFMediaType> closestSupportedType;
            //const auto result = typeHandler->IsMediaTypeSupported(inputType, &closestSupportedType);
            //if (result == MF_E_INVALIDMEDIATYPE)
            //{
            //  if (!closestSupportedType)
            //  {
            //      std::cerr << "Media type not supported" << std::endl;
            //      winHr(mediaSink->Shutdown());
            //      goto end; //:o
            //  }
            //  winHr(reader->SetCurrentMediaType(inStreamIndex, nullptr, closestSupportedType));
            //  winHr(typeHandler->SetCurrentMediaType(closestSupportedType));
            //  winHr(MFCreateSinkWriterFromMediaSink(mediaSink, nullptr, &writer));
            //  winHr(writer->SetInputMediaType(0, closestSupportedType, nullptr));
            //}
            //else 
            {
                //winHr(result);
                //winHr(reader->SetCurrentMediaType(inStreamIndex, nullptr, inputType));
                winHr(typeHandler->SetCurrentMediaType(inputType));
                winHr(MFCreateSinkWriterFromMediaSink(mediaSink, nullptr, &writer));
                winHr(writer->SetInputMediaType(0, inputType, nullptr));
            }
        }

        winHr(writer->BeginWriting());
        while (true)
        {
            ComPtr<IMFSample> sample;
            DWORD streamFlags;
            MFTIME timestamp;
            winHr(reader->ReadSample(inStreamIndex, 0, nullptr, &streamFlags, &timestamp, &sample));

            if (streamFlags & MF_SOURCE_READERF_ENDOFSTREAM)
            {
                winHr(writer->NotifyEndOfSegment(0));
                break;
            }
            if (streamFlags & MF_SOURCE_READERF_STREAMTICK)
                winHr(writer->SendStreamTick(0, timestamp));

            if (!sample) 
                continue;

            // SetSampleTime is redundant
            //winHr(sample->SetSampleTime(timestamp));
            winHr(writer->WriteSample(0, sample));
        }

        // Flush shouldn't be called! 
        // winHr(writer->Flush(0));

        std::cout << "(Press enter to stop)" << std::endl;
        std::cin.get();

        winHr(writer->Finalize());
        writer.attach(nullptr);
        winHr(mediaSink->Shutdown());
    }

end:
    winHr(MFShutdown());
    CoUninitialize();
}