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

标签 c++ audio ms-media-foundation flac

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

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

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

除了 FLAC 之外,目前一切正常。

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

但是使用 MP3 导出是有效的,因为 MF 平台似乎有某种后备并且正在使用具有 2 个 channel 、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,此对象不一致或不支持

我在这里做错了什么?

#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同样已过时,并且不反射(reflect)最近添加的内容。

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

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

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

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

enter image description here

作为引用,文件本身是:20180224-175524.flac .

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

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

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

示例文件:20180224-184012.mp4 . “FLAC(带框)”

enter image description here

总结一下:

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

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

编解码器显然与 Media Session 兼容API,大概适合与 Sink Writer 一起使用API。

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

关于c++ - 如何使用 Microsoft Media Foundation 将原始 48khz/32 位 PCM 编码为 FLAC?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48930499/

相关文章:

c++ - std::logic_error 类是不同类型的 std::invalid_argument,不是吗?

android - Android:按钮会在onClick播放2种声音

c# - 显示默认设备输出的音量峰值

c++ - 对迭代器进行操作的函数的每个容器优化

c++ - 与 .mm 源链接时 ObjC 框架的 undefined symbol

c# - 使用 C# 64 位进行安讯士相机视频流传输

c++ - 带有登录名和密码的开源 URL

audio - Windows Server 2008 R2上的虚拟音频设备和媒体基础

c++ - 无法让我的压缩算法正常运行

audio - react-native ios app webview 播放视频但没有音频