You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++/WinRT的MediaPlayer调用CopyFrameToVideoSurface提取帧崩溃如何修复

崩溃核心原因

你代码中默认构造的全局IDirect3DSurface、Streams::IBuffer都是空实例,底层没有分配对应的显存/内存缓冲区,CopyFrameToVideoSurface尝试往空地址写入帧数据时直接触发访问违规,就是你遇到的崩溃。同时全局对象的设计也存在线程安全问题,多播放器实例共用资源会直接冲突。


修复方案

方案1:修复现有D3D通路代码

首先你需要先初始化合法的D3D设备,再根据当前视频帧的尺寸动态创建匹配的Surface和缓冲区,不要使用全局空对象。

前置依赖

需要在项目中引入D3D11相关头文件,C++/WinRT依赖配置正确。

代码示例

#include <winrt/Windows.Graphics.DirectX.Direct3D11.h>
#include <winrt/Windows.Graphics.DirectX.h>
#include <d3d11.h>
#include <dxgi1_2.h>

using namespace winrt::Windows::Graphics::DirectX;
using namespace winrt::Windows::Graphics::DirectX::Direct3D11;
using namespace winrt::Windows::Storage::Streams;

// 全局初始化一次D3D设备即可,无需重复创建
IDirect3DDevice CreateD3DDevice() {
    winrt::com_ptr<ID3D11Device> d3d11Device;
    UINT createFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    winrt::check_hresult(D3D11CreateDevice(
        nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createFlags,
        nullptr, 0, D3D11_SDK_VERSION, d3d11Device.put(), nullptr, nullptr
    ));
    winrt::com_ptr<IDXGIDevice3> dxgiDevice = d3d11Device.as<IDXGIDevice3>();
    IDirect3DDevice device{};
    winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(
        dxgiDevice.get(), reinterpret_cast<IInspectable**>(winrt::put_abi(device))
    ));
    return device;
}

IDirect3DDevice g_d3d_device = CreateD3DDevice();

DLLEXPORT void PlayerSetFrameEventHandler(
    int32_t player_id, void (*callback)(uint8_t* buffer, int32_t size,
                                        int32_t width, int32_t height)) {
  auto& player = g_media_players.at(player_id);
  player.IsVideoFrameServerEnabled(true);
  player.VideoFrameAvailable([=](auto, const auto& args) -> void {
    try {
        // 获取当前视频帧的分辨率
        auto playback_item = player.PlaybackSession().PlaybackItem();
        if (playback_item.VideoTracks().Size() == 0) return;
        auto video_prop = playback_item.VideoTracks().GetAt(0).GetEncodingProperties();
        int32_t frame_width = static_cast<int32_t>(video_prop.Width());
        int32_t frame_height = static_cast<int32_t>(video_prop.Height());

        // 创建和帧尺寸匹配的D3D Surface
        auto surface = g_d3d_device.CreateSurface(
            frame_width, frame_height,
            DirectXPixelFormat::B8G8R8A8UIntNormalized,
            DirectXAlphaMode::Premultiplied
        );

        // 复制视频帧到Surface
        player.CopyFrameToVideoSurface(surface);

        // 转SoftwareBitmap并复制到缓冲区
        auto bitmap_op = SoftwareBitmap::CreateCopyFromSurfaceAsync(surface);
        bitmap_op.wait();
        SoftwareBitmap bitmap = bitmap_op.GetResults();
        if (!bitmap) return;

        Buffer buffer(frame_width * frame_height * 4);
        bitmap.CopyToBuffer(buffer);

        // 触发用户回调
        if (callback) {
            callback(buffer.data(), static_cast<int32_t>(buffer.Length()), frame_width, frame_height);
        }
    } catch (winrt::hresult_error const& e) {
        // 异常捕获避免崩溃,可自行添加日志
        OutputDebugStringW(e.message().c_str());
    }
  });
}

方案2:更简单的无D3D依赖方案

如果不需要GPU处理帧数据,可以直接从回调参数中获取VideoFrame,无需自己维护D3D设备,代码更简洁:

DLLEXPORT void PlayerSetFrameEventHandler(
    int32_t player_id, void (*callback)(uint8_t* buffer, int32_t size,
                                        int32_t width, int32_t height)) {
  auto& player = g_media_players.at(player_id);
  player.IsVideoFrameServerEnabled(true);
  player.VideoFrameAvailable([=](auto, const auto& args) -> void {
    try {
        VideoFrame frame = args.TryGetFrame(nullptr);
        if (!frame || !frame.SoftwareBitmap()) return;
        SoftwareBitmap bitmap = frame.SoftwareBitmap();

        int32_t frame_width = bitmap.PixelWidth();
        int32_t frame_height = bitmap.PixelHeight();
        Buffer buffer(frame_width * frame_height * 4);
        bitmap.CopyToBuffer(buffer);

        if (callback) {
            callback(buffer.data(), static_cast<int32_t>(buffer.Length()), frame_width, frame_height);
        }
    } catch (winrt::hresult_error const& e) {
        OutputDebugStringW(e.message().c_str());
    }
  });
}

性能优化建议

  • 如果追求性能,不要每次回调都新建Surface和Buffer,可以缓存实例,只有当视频分辨率发生变化时再重建资源,减少内存分配开销
  • VideoFrameAvailable回调在后台线程触发,如果回调中需要操作UI等线程受限资源,需要自行调度到目标线程执行
  • 帧数据默认是BGRA格式,如果你的渲染场景需要其他格式,可以用SoftwareBitmap::Convert方法转码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 04:27:03