#![cfg(target_os = "windows")]
use crate::gfx;
use gfx::d3d12;
use gfx::Device;
use crate::os;
use os::win32;
use std::result;
use windows::{
core::*, Win32::Foundation::*,
Win32::Graphics::Direct3D11::*, Win32::Graphics::Direct3D::*, Win32::Foundation::HINSTANCE,
Win32::Media::MediaFoundation::*, Win32::Graphics::Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM,
Win32::System::Com::CoCreateInstance, Win32::System::Com::CLSCTX_ALL,
};
pub struct VideoPlayer {
device: ID3D11Device,
media_engine_ex: IMFMediaEngineEx,
notify: *mut NotifyEvents,
texture: Option<d3d12::Texture>,
width: u32,
height: u32
}
pub struct NotifyEvents {
load_meta_data: bool,
paused: bool,
can_play: bool,
playing: bool,
ended: bool,
has_error: bool
}
impl NotifyEvents {
fn event(&mut self, event: u32) {
match MF_MEDIA_ENGINE_EVENT(event as i32) {
MF_MEDIA_ENGINE_EVENT_LOADEDMETADATA => {
self.load_meta_data = true;
},
MF_MEDIA_ENGINE_EVENT_CANPLAY => {
self.can_play = true;
}
MF_MEDIA_ENGINE_EVENT_PLAY => {
self.playing = true;
}
MF_MEDIA_ENGINE_EVENT_PAUSE => {
self.paused = true;
}
MF_MEDIA_ENGINE_EVENT_ENDED => {
self.ended = true;
self.playing = false;
}
MF_MEDIA_ENGINE_EVENT_TIMEUPDATE => {
}
MF_MEDIA_ENGINE_EVENT_ERROR => {
self.has_error = true;
}
_ => ()
}
}
}
#[allow(unused_must_use)]
mod imp {
use windows::Win32::Media::MediaFoundation::*;
#[windows_core::implement(IMFMediaEngineNotify)]
pub struct MediaEngineNotify {
pub notify: *mut super::NotifyEvents
}
#[allow(non_snake_case)]
impl IMFMediaEngineNotify_Impl for MediaEngineNotify_Impl {
fn EventNotify(&self, event: u32, _param1: usize, _param2: u32) -> ::windows::core::Result<()> {
unsafe {
(*self.notify).event(event);
}
Ok(())
}
}
}
pub use imp::*;
fn new_notify_events() -> *mut NotifyEvents {
unsafe {
let layout =
std::alloc::Layout::from_size_align(std::mem::size_of::<NotifyEvents>(), 8).unwrap();
std::alloc::alloc_zeroed(layout) as *mut NotifyEvents
}
}
impl VideoPlayer {
fn handle_error(&self) -> result::Result<(), super::Error> {
unsafe {
if (*self.notify).has_error {
let err = self.media_engine_ex.GetError()?;
let code = err.GetErrorCode();
let msgs = [
"MF_MEDIA_ENGINE_ERR_NOERROR",
"MF_MEDIA_ENGINE_ERR_ABORTED",
"MF_MEDIA_ENGINE_ERR_NETWORK",
"MF_MEDIA_ENGINE_ERR_DECODE",
"MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED",
"MF_MEDIA_ENGINE_ERR_ENCRYPTED",
];
let mut ext_str = "".to_string();
let ext = err.GetExtendedErrorCode();
if let Err(e) = &ext {
ext_str = e.message().to_string()
}
return Err(super::Error{
msg: format!("hotline_rs::av::wmf: {} : {}", msgs[code as usize], ext_str)
});
}
Ok(())
}
}
}
impl super::VideoPlayer<d3d12::Device> for VideoPlayer {
fn create(device: &d3d12::Device) -> result::Result<VideoPlayer, super::Error> {
let factory = d3d12::get_dxgi_factory(device);
let (adapter, _) = d3d12::get_hardware_adapter(factory, &Some(device.get_adapter_info().name.to_string())).unwrap();
unsafe {
MFStartup(MF_SDK_VERSION << 16 | MF_API_VERSION, 0)?;
let mut device : Option<ID3D11Device> = None;
D3D11CreateDevice(
&adapter,
D3D_DRIVER_TYPE_UNKNOWN,
HINSTANCE(std::ptr::null_mut() as *mut _),
D3D11_CREATE_DEVICE_VIDEO_SUPPORT | D3D11_CREATE_DEVICE_BGRA_SUPPORT,
None,
D3D11_SDK_VERSION,
Some(&mut device),
None,
None,
)?;
let device = device.unwrap();
let mt : ID3D11Multithread = device.cast()?;
mt.SetMultithreadProtected(BOOL::from(true)).expect("hotline_rs::wmf::error: call to SetMultithreadProtected failed");
let mut reset_token : u32 = 0;
let mut dxgi_manager : Option<IMFDXGIDeviceManager> = None;
MFCreateDXGIDeviceManager(&mut reset_token, &mut dxgi_manager)?;
let mut attributes : Option<IMFAttributes> = None;
MFCreateAttributes(&mut attributes, 1)?;
if let Some(attributes) = &attributes {
if let Some(dxgi_manager) = &dxgi_manager {
let idevice : IUnknown = device.cast()?;
dxgi_manager.ResetDevice(&idevice, reset_token)?;
let idxgi_manager : IUnknown = dxgi_manager.cast()?;
attributes.SetUnknown(&MF_MEDIA_ENGINE_DXGI_MANAGER, &idxgi_manager)?;
}
attributes.SetUINT32(&MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM.0 as u32)?;
let notify = new_notify_events();
let mn = MediaEngineNotify {
notify
};
let imn : IMFMediaEngineNotify = mn.into();
attributes.SetUnknown(&MF_MEDIA_ENGINE_CALLBACK, &imn)?;
let mf_factory : IMFMediaEngineClassFactory =
CoCreateInstance(&CLSID_MFMediaEngineClassFactory, None, CLSCTX_ALL)?;
let media_engine = mf_factory.CreateInstance(0, attributes)?;
let player = VideoPlayer {
device,
media_engine_ex: media_engine.cast()?,
notify,
texture: None,
width: 0,
height: 0
};
return Ok(player);
}
Err(super::Error {
msg: String::from("hotline_rs::av::wmf:: failed to initialise, could not create attributes"),
})
}
}
fn set_source(&mut self, filepath: String) -> result::Result<(), super::Error> {
unsafe {
(*self.notify).can_play = false;
(*self.notify).ended = false;
(*self.notify).playing = false;
let wp = win32::string_to_wide(filepath);
let bstr = SysAllocString(PCWSTR(wp.as_ptr() as _));
self.media_engine_ex.SetSource(&bstr)?;
Ok(())
}
}
fn play(&self) -> result::Result<(), super::Error> {
unsafe {
self.media_engine_ex.Play()?;
}
Ok(())
}
fn pause(&self) -> result::Result<(), super::Error> {
unsafe {
self.media_engine_ex.Pause()?;
}
Ok(())
}
fn update(&mut self, device: &mut d3d12::Device) -> result::Result<(), super::Error> {
unsafe {
self.handle_error()?;
if !self.is_loaded() {
return Ok(());
}
if self.texture.is_none() && self.is_loaded() {
let mut x : u32 = 0;
let mut y : u32 = 0;
self.media_engine_ex.GetNativeVideoSize(Some(&mut x), Some(&mut y))?;
let info = gfx::TextureInfo {
tex_type: gfx::TextureType::Texture2D,
format: gfx::Format::BGRA8n,
width: x as u64,
height: y as u64,
depth: 1,
array_layers: 1,
mip_levels: 1,
samples: 1,
usage: gfx::TextureUsage::VIDEO_DECODE_TARGET | gfx::TextureUsage::SHADER_RESOURCE,
initial_state: gfx::ResourceState::ShaderResource
};
self.texture = Some(device.create_texture::<u8>(&info, None)?);
self.width = x;
self.height = y;
}
if self.is_loaded() {
let pts = self.media_engine_ex.OnVideoStreamTick();
if pts.is_ok() {
if let Some(tex) = &self.texture {
let sh = d3d12::get_texture_shared_handle(tex);
if let Some(handle) = sh {
let dev1 : ID3D11Device1 = self.device.cast()?;
let media_texture : ID3D11Texture2D = dev1.OpenSharedResource1(*handle)?;
let mf_rect = MFVideoNormalizedRect {
left: 0.0,
top: 0.0,
right: 1.0,
bottom: 1.0
};
let rect = RECT {
left: 0,
top: 0,
right: self.width as i32,
bottom: self.height as i32
};
self.media_engine_ex.TransferVideoFrame(
&media_texture, Some(&mf_rect), &rect, None)?;
}
}
}
}
Ok(())
}
}
fn get_size(&self) -> os::Size<u32> {
os::Size {
x: self.width,
y: self.height
}
}
fn is_loaded(&self) -> bool {
unsafe {
(*self.notify).can_play
}
}
fn is_playing(&self) -> bool {
unsafe {
(*self.notify).playing
}
}
fn is_ended(&self) -> bool {
unsafe {
(*self.notify).ended
}
}
fn get_texture(&self) -> &Option<d3d12::Texture> {
&self.texture
}
}