如何使用 Microsoft Media Foundation 将原始 48khz/32 位 PCM 编码为 FLAC?

How do I encode raw 48khz/32bits PCM to FLAC using Microsoft Media Foundation?

我创建了一个能够使用 Microsoft 的媒体基础平台对视频和音频进行编码的 SinkWriter。

到目前为止,视频工作正常,但我在音频方面遇到了一些问题。

我的 PCM 源的采样率为 48828hz,每个样本 32 位并且是单声道。

目前一切正常,除了 FLAC。

例如,MP3 输出或多或少可以工作,但格式错误。关于 MSDN (MP3 Audio Encoder),MP3 编码器仅支持每个样本 16 位作为输入。如上所述,我的 PCM 源每个样本有 32 位。

然而,MP3 导出工作正常,因为 MF 平台似乎有某种后备,并且正在使用具有 2 个通道、32khz 和 320kb/s 比特率的 MPEG 音频层 1/2 (mpga)。

当我将 MF_MT_SUBTYPE 设置为 MFAudioFormat_FLAC 时,事情开始变得奇怪起来。导出也正常,但音频质量很差。有很多噪音,但我能够识别音频。关于 VLC,FLAC 文件的采样率为 44.1khz,每个样本 8 位并且是单声道。

这是否意味着 FLAC 编解码器无法与我提供的 PCM 一起使用?

有没有人遇到同样的问题并且能够解决它?

更新

在对这个问题做了更多研究之后,我的 32 位分辨率的 PCM 音频似乎太高了。所以目前我正在尝试将 32 位 PCM 转换为 FLAC 的 24 位和 MP3 的 16 位,但到目前为止还没有成功。如果我取得了一些进展,我会及时通知您。

--------

更新 2

我创建了一个最小的示例应用程序来展示我面临的问题。 它读取 48khz32bit wave 文件并尝试将其编码为 flac。

执行 hr = pSinkWriter->BeginWriting(); 命令时出现错误 0xc00d36b4,这意味着 The data specified for the media type is invalid, inconsistent, or not supported by this object

我做错了什么?

#include "stdafx.h"

#include <windows.h>
#include <windowsx.h>

#include <comdef.h>

#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <Mferror.h>

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

using namespace System;


int main(array<System::String ^> ^args)
{
    HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);

    hr = MFStartup(MF_VERSION);

    IMFMediaType *pMediaType;
    IMFMediaType *pMediaTypeOut;
    IMFSourceReader *pSourceReader;
    IMFAttributes *pAttributes;
    IMFSinkWriter *pSinkWriter;

    hr = MFCreateSourceReaderFromURL(
        L"C:\Temp\48khz32bit.wav",
        NULL,
        &pSourceReader
    );

    hr = MFCreateAttributes(&pAttributes, 1);

    hr = pAttributes->SetGUID(
        MF_TRANSCODE_CONTAINERTYPE,
        MFTranscodeContainerType_WAVE
    );

    hr = MFCreateSinkWriterFromURL(
        L"C:\Temp\foo.flac",
        NULL,
        pAttributes,
        &pSinkWriter
    );

    hr = pSourceReader->GetCurrentMediaType(
        MF_SOURCE_READER_FIRST_AUDIO_STREAM,
        &pMediaType);

    hr = MFCreateMediaType(&pMediaTypeOut);

    hr = pMediaTypeOut->SetGUID(
        MF_MT_MAJOR_TYPE,
        MFMediaType_Audio
    );

    hr = pMediaTypeOut->SetGUID(
        MF_MT_SUBTYPE,
        MFAudioFormat_FLAC
    );

    hr = pMediaTypeOut->SetUINT32(
        MF_MT_AUDIO_SAMPLES_PER_SECOND,
        48000
    );

    hr = pMediaTypeOut->SetUINT32(
        MF_MT_AUDIO_NUM_CHANNELS,
        1
    );

    hr = pMediaTypeOut->SetUINT32(
        MF_MT_AUDIO_BITS_PER_SAMPLE,
        32
    );

    hr = pMediaTypeOut->SetUINT32(
        MF_MT_AUDIO_AVG_BYTES_PER_SECOND,
        (((32 + 7) / 8) * 1) * 48000
    );

    hr = pMediaTypeOut->SetUINT32(
        MF_MT_AUDIO_BLOCK_ALIGNMENT,
        ((32 + 7) / 8) * 1
    );

    DWORD nWriterStreamIndex = -1;

    hr = pSinkWriter->AddStream(pMediaTypeOut, &nWriterStreamIndex);

    hr = pSinkWriter->BeginWriting();

    _com_error err(hr);
    LPCTSTR errMsg = err.ErrorMessage();

    for (;;)
    {
        DWORD nStreamIndex, nStreamFlags;
        LONGLONG nTime;
        IMFSample *pSample;

        hr = pSourceReader->ReadSample(
            MF_SOURCE_READER_FIRST_AUDIO_STREAM,
            0,
            &nStreamIndex,
            &nStreamFlags,
            &nTime,
            &pSample);

        if (pSample)
        {
            OutputDebugString(L"Write sample...\n");

            hr = pSinkWriter->WriteSample(
                nWriterStreamIndex,
                pSample
            );
        }

        if (nStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM)
        {
            break;
        }
    }

    hr = pSinkWriter->Finalize();

    return 0;
}

--------

更新 3

我添加了解决方案作为答案。

--------

初始化SinkWriter

HRESULT SinkWriter::InitializeSinkWriter(IMFSinkWriter **ppWriter, DWORD *pStreamIndex, DWORD *pAudioStreamIndex, LPCWSTR filename)
{
    *ppWriter = NULL;
    *pStreamIndex = NULL;
    *pAudioStreamIndex = NULL;

    IMFSinkWriter   *pSinkWriter = NULL;

    // Attributes
    IMFAttributes   *pAttributes;

    HRESULT hr = S_OK;

    DX::ThrowIfFailed(
        MFCreateAttributes(
            &pAttributes,
            3
        )
    );

#if defined(ENABLE_HW_ACCELERATION)
    CComPtr<ID3D11Device> device;
    D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0 };

#if defined(ENABLE_HW_DRIVER)
    DX::ThrowIfFailed(
        D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            nullptr,
            (0 * D3D11_CREATE_DEVICE_SINGLETHREADED) | D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
            levels,
            ARRAYSIZE(levels),
            D3D11_SDK_VERSION,
            &device,
            nullptr,
            nullptr
        )
    );

    const CComQIPtr<ID3D10Multithread> pMultithread = device;
    pMultithread->SetMultithreadProtected(TRUE);
#else
    DX::ThrowIfFailed(
        D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_NULL,
            nullptr,
            D3D11_CREATE_DEVICE_SINGLETHREADED,
            levels,
            ARRAYSIZE(levels),
            D3D11_SDK_VERSION,
            &device,
            nullptr,
            nullptr)
    );
#endif

    UINT token;
    CComPtr<IMFDXGIDeviceManager> pManager;

    DX::ThrowIfFailed(
        MFCreateDXGIDeviceManager(
            &token,
            &pManager
        )
    );

    DX::ThrowIfFailed(
        pManager->ResetDevice(
            device,
            token
        )
    );

    DX::ThrowIfFailed(
        pAttributes->SetUnknown(
            MF_SOURCE_READER_D3D_MANAGER,
            pManager
        )
    );

    DX::ThrowIfFailed(
        pAttributes->SetUINT32(
            MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS,
            TRUE
        )
    );

#if (WINVER >= 0x0602)
    DX::ThrowIfFailed(
        pAttributes->SetUINT32(
            MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING,
            TRUE
        )
    );
#endif
#else
    DX::ThrowIfFailed(
        pAttributes->SetUINT32(
            MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS,
            TRUE
        )
    );

    DX::ThrowIfFailed(
        pAttributes->SetUINT32(
            MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING,
            TRUE
        )
    );
#endif

    DX::ThrowIfFailed(
        MFCreateSinkWriterFromURL(
            filename,
            NULL,
            pAttributes,
            &pSinkWriter
        )
    );

    if (m_vFormat != VideoFormat::SWFV_NONE)
    {
        DX::ThrowIfFailed(
            InitializeVideoCodec(
                pSinkWriter,
                pStreamIndex
            )
        );
    }

    if (m_audFormat != AudioFormat::SWAF_NONE)
    {
        DX::ThrowIfFailed(
            InitializeAudioCodec(
                pSinkWriter,
                pAudioStreamIndex
            )
        );
    }

    // Tell the sink writer to start accepting data.
    DX::ThrowIfFailed(
        pSinkWriter->BeginWriting()
    );

    // Return the pointer to the caller.
    *ppWriter = pSinkWriter;
    (*ppWriter)->AddRef();

    SAFE_RELEASE(pSinkWriter);
    return hr;
}

初始化音频编解码器

HRESULT SinkWriter::InitializeAudioCodec(IMFSinkWriter *pSinkWriter, DWORD *pStreamIndex)
{
    // Audio media types
    IMFMediaType    *pAudioTypeOut = NULL;
    IMFMediaType    *pAudioTypeIn = NULL;

    DWORD           audioStreamIndex;

    HRESULT hr = S_OK;

    // Set the output audio type.
    DX::ThrowIfFailed(
        MFCreateMediaType(
            &pAudioTypeOut
        )
    );

    DX::ThrowIfFailed(
        pAudioTypeOut->SetGUID(
            MF_MT_MAJOR_TYPE, 
            MFMediaType_Audio
        )
    );

    DX::ThrowIfFailed(
        pAudioTypeOut->SetGUID(
            MF_MT_SUBTYPE,
            AUDIO_SUBTYPE
        )
    );

    DX::ThrowIfFailed(
        pSinkWriter->AddStream(
            pAudioTypeOut,
            &audioStreamIndex
        )
    );

    // Set the input audio type
    DX::ThrowIfFailed(
        MFCreateMediaType(
            &pAudioTypeIn
        )
    );

    DX::ThrowIfFailed(
        pAudioTypeIn->SetGUID(
            MF_MT_MAJOR_TYPE,
            AUDIO_MAJOR_TYPE
        )
    );

    DX::ThrowIfFailed(
        pAudioTypeIn->SetGUID(
            MF_MT_SUBTYPE,
            MFAudioFormat_PCM
        )
    );

    DX::ThrowIfFailed(
        pAudioTypeIn->SetUINT32(
            MF_MT_AUDIO_NUM_CHANNELS,
            AUDIO_NUM_CHANNELS
        )
    );

    DX::ThrowIfFailed(
        pAudioTypeIn->SetUINT32(
            MF_MT_AUDIO_BITS_PER_SAMPLE,
            AUDIO_BITS_PER_SAMPLE
        )
    );

    DX::ThrowIfFailed(
        pAudioTypeIn->SetUINT32(
            MF_MT_AUDIO_BLOCK_ALIGNMENT,
            AUDIO_BLOCK_ALIGNMENT
        )
    );

    DX::ThrowIfFailed(
        pAudioTypeIn->SetUINT32(
            MF_MT_AUDIO_SAMPLES_PER_SECOND,
            AUDIO_SAMPLES_PER_SECOND
        )
    );

    DX::ThrowIfFailed(
        pAudioTypeIn->SetUINT32(
            MF_MT_AUDIO_AVG_BYTES_PER_SECOND,
            AUDIO_AVG_BYTES_PER_SECOND
        )
    );

    DX::ThrowIfFailed(
        pSinkWriter->SetInputMediaType(
            audioStreamIndex,
            pAudioTypeIn,
            NULL
        )
    );

    *pStreamIndex = audioStreamIndex;

    SAFE_RELEASE(pAudioTypeOut);
    SAFE_RELEASE(pAudioTypeIn);

    return hr;
}

推送音频数据

HRESULT SinkWriter::PushAudio(UINT32* data)
{
    HRESULT hr = S_FALSE;

    if (m_isInitializing)
    {
        return hr;
    }

    IMFSample *pSample = NULL;
    IMFMediaBuffer *pBuffer = NULL;
    BYTE *pMem = NULL;

    size_t cbBuffer = m_bufferLength * sizeof(short);   

    // Create a new memory buffer.
    hr = MFCreateMemoryBuffer(cbBuffer, &pBuffer);

    // Lock the buffer and copy the audio frame to the buffer.
    if (SUCCEEDED(hr))
    {
        hr = pBuffer->Lock(&pMem, NULL, NULL);
    }

    if (SUCCEEDED(hr))
    {
        CopyMemory(pMem, data, cbBuffer);
    }

    if (pBuffer)
    {
        pBuffer->Unlock();
    }

    if (m_vFormat == VideoFormat::SWFV_NONE && m_audFormat == AudioFormat::SWAF_WAV)
    {
        DWORD cbWritten = 0;

        if (SUCCEEDED(hr))
        {
            hr = m_pByteStream->Write(pMem, cbBuffer, &cbWritten);
        }

        if (SUCCEEDED(hr))
        {
            m_cbWrittenByteStream += cbWritten;
        }
    }
    else
    {
        // Set the data length of the buffer.
        if (SUCCEEDED(hr))
        {
            hr = pBuffer->SetCurrentLength(cbBuffer);
        }

        // Create media sample and add the buffer to the sample.
        if (SUCCEEDED(hr))
        {
            hr = MFCreateSample(&pSample);
        }

        if (SUCCEEDED(hr))
        {
            hr = pSample->AddBuffer(pBuffer);
        }

        // Set the timestamp and the duration.
        if (SUCCEEDED(hr))
        {
            hr = pSample->SetSampleTime(m_cbRtStartVideo);
        }

        if (SUCCEEDED(hr))
        {
            hr = pSample->SetSampleDuration(m_cbRtDurationVideo);
        }

        // Send the sample to the Sink Writer
        if (SUCCEEDED(hr))
        {
            hr = m_pSinkWriter->WriteSample(m_audioStreamIndex, pSample);
        }

        /*if (SUCCEEDED(hr))
        {
            m_cbRtStartAudio += m_cbRtDurationAudio;
        }*/

        SAFE_RELEASE(pSample);
        SAFE_RELEASE(pBuffer);
    }

    return hr;
}

因此,Microsoft 在 Windows 10 中引入了 FLAC 媒体基础转换 (MFT) 编码器 CLSID_CMSFLACEncMFT,但该编解码器目前仍未记录。

Supported Media Formats in Media Foundation 同样已经过时并且没有反映最近添加的内容。

我不知道对此有任何评论,我的意见是编解码器是为内部使用而添加的,但实现只是一个标准的媒体基础组件,没有许可限制,所以编解码器也不受限制,因为例如,field of use 限制。

此库存编解码器似乎仅限于 8、16 和 24 位 PCM 输入选项(即,不是 32 bits/sample - 您需要分别 resample)。编解码器能够接受多达 8 个通道和灵活的每秒采样率(48828 Hz 可以)。

即使编解码器(转换)似乎可以工作,如果你想生成一个文件,你还需要一个合适的容器格式(多路复用器)兼容MFAudioFormat_FLAC(标识符有7个结果在 Google 在 post 的时刻搜索,这基本上意味着甚至没有人知道编解码器)。过时的文档并未反映库存媒体接收器对 FLAC 的实际支持。

我借用了一个自定义媒体接收器,它将原始 MFT 输出有效负载写入文件,并且可以播放此类 FLAC 输出,因为 FLAC 帧包含解析比特流以进行播放的必要信息。

作为参考,文件本身是:20180224-175524.flac.

股票媒体接收器中的一个明显候选 WAVE Media Sink 无法接受 FLAC 输入。尽管如此,它可能会实现,但可能仅限于更简单的音频格式。

AVI 媒体接收器可能采用 FLAC 音频,但似乎无法创建纯音频 AVI。

然而,在其他媒体接收器中,有一个可以处理 FLAC 的媒体接收器:MPEG-4 File Sink。同样,尽管文档已过时,但媒体接收器采用 FLAC 输入,因此您应该能够创建带有 FLAC 音轨的 .MP4 文件。

示例文件:20180224-184012.mp4。 "FLAC (framed)"

总结一下:

  • FLAC 编码器 MFT 存在于 Windows10 中,可供使用;虽然缺少适当的文档
  • 需要注意将输入转换为兼容格式(不直接支持 32 位 PCM)
  • 可以直接管理MFT,消费MFT输出,然后获取FLAC码流
  • 或者,可以使用标准 MP4 媒体接收器生成带有 FLAC 音轨的输出
  • 或者,可以开发自定义媒体接收器并使用来自上游编码器连接的 FLAC 比特流

编解码器可能与 Transcode API 兼容,但上述限制适用。容器类型需要特别是MFTranscodeContainerType_MPEG4

编解码器显然与 Media Session API, presumably it is good for use with Sink Writer API 兼容。

在您的代码中,当您尝试使用 Sink Writer API 时,您应该类似地在您的代码中将输入可能转换为兼容格式的 MP4 输出(兼容 PCM 或兼容 FLAC,编码器 MFT 在您这边管理).了解 MP4 媒体接收器总体上能够创建 FLAC 音轨,您应该能够调试代码中的细节并使组件协同工作。

终于解决了这个问题。说实话并不难。但是,如果您知道如何取得成就,情况总是如此 ;)。

我在下面创建了一个复制和粘贴示例,以说明如何使用 Microsoft Media Foundation 实现 FLAC 编码。

缺少的一块拼图是 MFTranscodeGetAudioOutputAvailableTypes。此函数列出了音频编码器的所有可用输出格式。

如果您不确定操作系统支持哪些MFT,您可以先调用MFTEnumEx function。这为您提供了所有可用 MFT 的列表。在我使用 windows 10 的情况下,有这样定义的 FLAC MFT。

Name: Microsoft FLAC Audio Encoder MFT
Input Types: 1 items:
    Audio-PCM
Class identifier: 128509e9-c44e-45dc-95e9-c255b8f466a6
Output Types: 1 items:
    Audio-0000f1ac-0000-0010-8000-00aa00389b71
Transform Flags: 1
Transform Category: Audio Encoder

所以我接下来要做的是创建源 reader 并获取当前媒体类型。对我来说重要的值是采样率、比特率和通道。

然后我创建了一个 GetOutputMediaTypes 函数,它需要请求的音频格式、采样率、比特率、通道和对 IMFMediaType 的引用。

MFTranscodeGetAudioOutputAvailableTypes 函数 return 提供 MFAudioFormat_flac GUID 的所有可用类型。

在使用 hr = pAvailableTypes->GetElementCount(&dwMTCount); 获取可用媒体类型的计数后,我可以遍历它们并检查一种类型是否支持我的请求。如果是这样,我 return 媒体类型。

最后一部分是最简单的。

首先将输出媒体类型添加到sinkwriter中获取流索引。

DWORD dwWriterStreamIndex = -1;

// Add the stream
hr = pSinkWriter->AddStream(
    pOuputMediaType,
    &dwWriterStreamIndex
);

然后设置输入类型并调用pSinkWriter->BeginWriting();让sinkwriter开始接受数据。

// Set input media type
hr = pSinkWriter->SetInputMediaType(
    dwWriterStreamIndex,
    pInputType,
    NULL
);

// Tell the sink writer to accept data
hr = pSinkWriter->BeginWriting();

如果输出和输入媒体类型设置正确,BeginWriting 应该 return 0 作为 HRESULT。

我们应该不会出错,因为我们正在使用函数 MFTranscodeGetAudioOutputAvailableTypes 提供的媒体类型。

最后一步是从源中读取所有样本reader并通过sinkwriter写入flac容器。

完成:)

我希望我能帮助解决这个问题。

也感谢 Roman R.

更新

此示例仅适用于从 4 位到 24 位的 Audio-PCM 格式。如果你想编码一个 32 位 Audio-PCM 你必须先重新采样然后编码它。

--------

这是最小的示例应用程序。

#include <windows.h>
#include <windowsx.h>

#include <atlstr.h>
#include <comdef.h>
#include <exception>

#include <mfapi.h>
#include <mfplay.h>
#include <mfreadwrite.h>
#include <mmdeviceapi.h>
#include <Audioclient.h>
#include <mferror.h>
#include <Wmcodecdsp.h>

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


inline void ThrowIfFailed(HRESULT hr)
{
    if (FAILED(hr))
    {
        // Get the error message
        _com_error err(hr);
        LPCTSTR errMsg = err.ErrorMessage();

        OutputDebugString(L"################################## ERROR ##################################\n");
        OutputDebugString(errMsg);
        OutputDebugString(L"\n################################## ----- ##################################\n");

        CStringA sb(errMsg);
        // Set a breakpoint on this line to catch DirectX API errors
        throw std::exception(sb);
    }
}

template <class T> void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = nullptr;
    }
}

using namespace System;

HRESULT GetOutputMediaTypes(
    GUID cAudioFormat,
    UINT32 cSampleRate,
    UINT32 cBitPerSample,
    UINT32 cChannels,
    IMFMediaType **ppType
)
{
    // Enumerate all codecs except for codecs with field-of-use restrictions.
    // Sort the results.
    DWORD dwFlags =
        (MFT_ENUM_FLAG_ALL & (~MFT_ENUM_FLAG_FIELDOFUSE)) |
        MFT_ENUM_FLAG_SORTANDFILTER;

    IMFCollection   *pAvailableTypes = NULL;    // List of audio media types.
    IMFMediaType    *pAudioType = NULL;         // Corresponding codec.

    HRESULT hr = MFTranscodeGetAudioOutputAvailableTypes(
        cAudioFormat,
        dwFlags,
        NULL,
        &pAvailableTypes
    );

    // Get the element count.
    DWORD dwMTCount;
    hr = pAvailableTypes->GetElementCount(&dwMTCount);

    // Iterate through the results and check for the corresponding codec.
    for (DWORD i = 0; i < dwMTCount; i++)
    {
        hr = pAvailableTypes->GetElement(i, (IUnknown**)&pAudioType);

        GUID majorType;
        hr = pAudioType->GetMajorType(&majorType);

        GUID subType;
        hr = pAudioType->GetGUID(MF_MT_SUBTYPE, &subType);

        if (majorType != MFMediaType_Audio || subType != MFAudioFormat_FLAC)
        {
            continue;
        }

        UINT32 sampleRate = NULL;
        hr = pAudioType->GetUINT32(
            MF_MT_AUDIO_SAMPLES_PER_SECOND,
            &sampleRate
        );

        UINT32 bitRate = NULL;
        hr = pAudioType->GetUINT32(
            MF_MT_AUDIO_BITS_PER_SAMPLE,
            &bitRate
        );

        UINT32 channels = NULL;
        hr = pAudioType->GetUINT32(
            MF_MT_AUDIO_NUM_CHANNELS,
            &channels
        );

        if (sampleRate == cSampleRate
            && bitRate == cBitPerSample
            && channels == cChannels)
        {
            // Found the codec.
            // Jump out!
            break;
        }       
    }

    // Add the media type to the caller
    *ppType = pAudioType;
    (*ppType)->AddRef();
    SafeRelease(&pAudioType);

    return hr;
}

int main(array<System::String ^> ^args)
{
    HRESULT hr = S_OK;

    // Initialize com interface
    ThrowIfFailed(
        CoInitializeEx(0, COINIT_MULTITHREADED)
    );

    // Start media foundation
    ThrowIfFailed(
        MFStartup(MF_VERSION)
    );

    IMFMediaType        *pInputType = NULL;
    IMFSourceReader     *pSourceReader = NULL;
    IMFMediaType        *pOuputMediaType = NULL;
    IMFSinkWriter       *pSinkWriter = NULL;

    // Create source reader
    hr = MFCreateSourceReaderFromURL(
        L"C:\Temp\48khz24bit.wav",
        NULL,
        &pSourceReader
    );

    // Create sink writer
    hr = MFCreateSinkWriterFromURL(
        L"C:\Temp\foo.flac",
        NULL,
        NULL,
        &pSinkWriter
    );

    // Get media type from source reader
    hr = pSourceReader->GetCurrentMediaType(
        MF_SOURCE_READER_FIRST_AUDIO_STREAM,
        &pInputType
    );

    // Get sample rate, bit rate and channels
    UINT32 sampleRate = NULL;
    hr = pInputType->GetUINT32(
        MF_MT_AUDIO_SAMPLES_PER_SECOND,
        &sampleRate
    );

    UINT32 bitRate = NULL;
    hr = pInputType->GetUINT32(
        MF_MT_AUDIO_BITS_PER_SAMPLE,
        &bitRate
    );

    UINT32 channels = NULL;
    hr = pInputType->GetUINT32(
        MF_MT_AUDIO_NUM_CHANNELS,
        &channels
    );

    // Try to find a media type that is fitting.
    hr = GetOutputMediaTypes(
        MFAudioFormat_FLAC,
        sampleRate,
        bitRate,
        channels,
        &pOuputMediaType);

    DWORD dwWriterStreamIndex = -1;

    // Add the stream
    hr = pSinkWriter->AddStream(
        pOuputMediaType,
        &dwWriterStreamIndex
    );

    // Set input media type
    hr = pSinkWriter->SetInputMediaType(
        dwWriterStreamIndex,
        pInputType,
        NULL
    );

    // Tell the sink writer to accept data
    hr = pSinkWriter->BeginWriting();

    // Forever alone loop
    for (;;)
    {
        DWORD nStreamIndex, nStreamFlags;
        LONGLONG nTime;
        IMFSample *pSample;

        // Read through the samples until...
        hr = pSourceReader->ReadSample(
            MF_SOURCE_READER_FIRST_AUDIO_STREAM,
            0,
            &nStreamIndex,
            &nStreamFlags,
            &nTime,
            &pSample);

        if (pSample)
        {
            OutputDebugString(L"Write sample...\n");

            hr = pSinkWriter->WriteSample(
                dwWriterStreamIndex,
                pSample
            );
        }

        // ... we are at the end of the stream...
        if (nStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM)
        {
            // ... and jump out.
            break;
        }
    }

    // Call finalize to finish writing.
    hr = pSinkWriter->Finalize();

    // Done :D
    return 0;
}