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

How to play audio using Sink Writer from Microsoft Media Foundation

我正在尝试使用 Microsoft Media Foundation 创建音频可视化工具。为此,我需要拦截样本并同时播放它们。使用带拓扑的媒体会话和样本采集器接收器似乎不切实际且过于复杂,因此我尝试为此使用接收器 Reader 和接收器写入器的组合(请参见图像的右半部分Overview of the Media Foundation Architecture). Unfortunately, Audio/Video Playback 并没有真正解释如何做到这一点。Developing Microsoft Media Foundation Applications 一书在第 92 页包含一个 source-to-sink 循环,但这仍然没有真正帮助我.

创建源 Reader 工作正常,我正在读取非零样本。将它们写入 Sink Writer(使用流式音频渲染器)不会给我任何错误,但我什么也没听到。我尝试了多种方法,例如选择其他媒体类型并明确选择渲染设备(尽管我只有一个,正如它所指示的那样),但无济于事。请注意,使用媒体会话播放音频效果很好!

我的代码基于这个问题:

这是我此时的代码:

#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:显然它在我创建编写器时很重要,所以现在我只在最后一刻才这样做,但现在我得到了 "Media type not supported" 错误。也许我需要手动选择一些媒体类型,但我稍后会研究这个——除非有人知道答案。

对于 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. 枚举 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();
}