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
相关产品推荐
相关产品推荐

