You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何在Visual Win32(C++)项目中实现MFMediaEngine本地路径设为源URL?

当然可行啦!Win32 C++项目里完全可以通过自定义IMByteStream来给MFMediaEngine提供本地磁盘的媒体内容——毕竟Media Foundation(MF)本身就是Win32平台原生支持的多媒体框架,MFMediaEngineIMByteStream都是它的标准接口,只要跟着规范来实现就行。下面我给你拆解具体的实现思路和关键代码片段:

在Win32 C++项目中通过IMByteStream给MFMediaEngine设置本地媒体源

可行性说明

完全没问题。你提到的示例虽然是UWP的,但MF是跨Win32和UWP的通用多媒体框架,IMByteStream作为MF的字节流抽象接口,在Win32环境下同样可以实现和使用,用来封装本地文件的读取逻辑,再传递给MFMediaEngine作为媒体源。

实现思路与步骤

1. 自定义IMByteStream实现类

首先你需要写一个继承自IMByteStream的类,把本地文件的读取逻辑封装进去。这个类必须实现IMByteStream的所有纯虚方法,核心要处理好ReadSeek这些和文件读写相关的操作,同时严格遵循COM接口的引用计数规则。

关键代码示例:

class CFileByteStream : public IMByteStream {
private:
    LONG m_cRef;
    HANDLE m_hFile;
    LONGLONG m_currentPosition;

public:
    CFileByteStream() : m_cRef(1), m_hFile(INVALID_HANDLE_VALUE), m_currentPosition(0) {}
    ~CFileByteStream() { 
        if (m_hFile != INVALID_HANDLE_VALUE) 
            CloseHandle(m_hFile); 
    }

    // IUnknown接口实现
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override {
        if (riid == IID_IUnknown || riid == IID_IMByteStream) {
            *ppv = static_cast<IMByteStream*>(this);
            AddRef();
            return S_OK;
        }
        *ppv = nullptr;
        return E_NOINTERFACE;
    }

    STDMETHODIMP_(ULONG) AddRef() override { 
        return InterlockedIncrement(&m_cRef); 
    }

    STDMETHODIMP_(ULONG) Release() override {
        ULONG cRef = InterlockedDecrement(&m_cRef);
        if (cRef == 0) delete this;
        return cRef;
    }

    // 核心读取方法
    STDMETHODIMP Read(BYTE* pbBuffer, ULONG cbBuffer, ULONG* pcbRead) override {
        if (!pbBuffer || !pcbRead) return E_POINTER;
        *pcbRead = 0;
        if (m_hFile == INVALID_HANDLE_VALUE) return E_UNEXPECTED;

        DWORD dwRead = 0;
        if (!ReadFile(m_hFile, pbBuffer, cbBuffer, &dwRead, nullptr)) {
            return HRESULT_FROM_WIN32(GetLastError());
        }
        m_currentPosition += dwRead;
        *pcbRead = dwRead;
        return S_OK;
    }

    // 写入方法(不需要的话直接返回未实现)
    STDMETHODIMP Write(const BYTE* pbBuffer, ULONG cbBuffer, ULONG* pcbWritten) override {
        return E_NOTIMPL;
    }

    // 定位方法
    STDMETHODIMP Seek(LONGLONG llPosition, DWORD dwOrigin, LONGLONG* pllNewPosition) override {
        if (!pllNewPosition) return E_POINTER;
        if (m_hFile == INVALID_HANDLE_VALUE) return E_UNEXPECTED;

        LARGE_INTEGER li;
        li.QuadPart = llPosition;
        DWORD dwMethod = FILE_BEGIN;
        switch (dwOrigin) {
            case STREAM_SEEK_CUR: dwMethod = FILE_CURRENT; break;
            case STREAM_SEEK_END: dwMethod = FILE_END; break;
            case STREAM_SEEK_SET: dwMethod = FILE_BEGIN; break;
            default: return E_INVALIDARG;
        }

        li.QuadPart = SetFilePointerEx(m_hFile, li, nullptr, dwMethod);
        if (li.QuadPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) {
            return HRESULT_FROM_WIN32(GetLastError());
        }
        m_currentPosition = li.QuadPart;
        *pllNewPosition = m_currentPosition;
        return S_OK;
    }

    // 获取文件大小
    STDMETHODIMP GetSize(LONGLONG* pllSize) override {
        if (!pllSize) return E_POINTER;
        if (m_hFile == INVALID_HANDLE_VALUE) return E_UNEXPECTED;

        LARGE_INTEGER li;
        if (!GetFileSizeEx(m_hFile, &li)) {
            return HRESULT_FROM_WIN32(GetLastError());
        }
        *pllSize = li.QuadPart;
        return S_OK;
    }

    // 标记为只读
    STDMETHODIMP IsReadOnly(BOOL* pfReadOnly) override {
        if (!pfReadOnly) return E_POINTER;
        *pfReadOnly = TRUE;
        return S_OK;
    }

    // 其他不需要的方法直接返回未实现
    STDMETHODIMP Clone(IMByteStream** ppStream) override { return E_NOTIMPL; }
    STDMETHODIMP Flush() override { return S_OK; }
};

2. 初始化MF并关联自定义字节流到MediaEngine

接下来要在Win32项目里完成MF框架的初始化,创建MFMediaEngine实例,再把我们自定义的字节流设置成它的媒体源。

关键步骤代码:

HRESULT SetupMediaEngine(HWND hRenderWnd, const wchar_t* localFilePath) {
    // 初始化Media Foundation
    HRESULT hr = MFStartup(MF_VERSION);
    if (FAILED(hr)) return hr;

    // 打开本地文件
    HANDLE hFile = CreateFileW(localFilePath, GENERIC_READ, FILE_SHARE_READ, 
                               nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (hFile == INVALID_HANDLE_VALUE) {
        MFShutdown();
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // 创建自定义字节流实例并绑定文件句柄
    CFileByteStream* pFileStream = new CFileByteStream();
    pFileStream->m_hFile = hFile; // 也可以封装成类的Open方法,更规范

    // 配置MediaEngine创建参数
    MF_MEDIA_ENGINE_CREATE_ATTRS createAttrs = {};
    createAttrs.cbSize = sizeof(MF_MEDIA_ENGINE_CREATE_ATTRS);
    createAttrs.hWnd = hRenderWnd; // 用于视频渲染的窗口句柄
    createAttrs.fEnableAudio = TRUE;
    createAttrs.fEnableVideo = TRUE;

    IMFMediaEngine* pMediaEngine = nullptr;
    IMFMediaEngineClassFactory* pFactory = nullptr;

    // 获取MediaEngine类工厂
    hr = CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr, CLSCTX_ALL, 
                          IID_PPV_ARGS(&pFactory));
    if (FAILED(hr)) goto Cleanup;

    // 创建MediaEngine实例
    hr = pFactory->CreateMediaEngine(&createAttrs, &pMediaEngine);
    if (FAILED(hr)) goto Cleanup;

    // 将自定义字节流设置为MediaEngine的源(MIME类型传nullptr让MF自动检测)
    hr = pMediaEngine->SetSourceFromByteStream(pFileStream, nullptr);
    if (FAILED(hr)) goto Cleanup;

    // 启动播放
    hr = pMediaEngine->Play();

Cleanup:
    if (pFileStream) pFileStream->Release();
    if (pFactory) pFactory->Release();
    // 注意:MediaEngine实例需要保留到播放结束后再释放,这里可以把它存为全局/类成员变量
    return hr;
}

3. 注意事项

  • 项目要链接MF相关库:mfplat.libmf.libmfuuid.lib,可以在项目属性的链接器输入里添加。
  • 严格遵守COM接口的引用计数规则,避免内存泄漏。
  • 如果遇到某些媒体格式无法播放,需要确保系统已经安装了对应的MF解码器。
  • 所有COM接口调用和文件操作都要做好错误检查,避免崩溃。

内容的提问来源于stack exchange,提问作者novice

火山引擎 最新活动