Skip to content

Instantly share code, notes, and snippets.

@Plecra
Last active September 2, 2021 19:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Plecra/775ea0c779d49e4de6f0090bd0bb32d4 to your computer and use it in GitHub Desktop.
Save Plecra/775ea0c779d49e4de6f0090bd0bb32d4 to your computer and use it in GitHub Desktop.
#![allow(non_snake_case)]
use std::path::Path;
use std::cell::Cell;
use std::ffi::c_void;
use std::sync::atomic::{AtomicU32, Ordering};
use bindings::Windows::Win32::{
Foundation::{HWND, LPARAM, PWSTR, WPARAM, E_UNEXPECTED, E_NOTIMPL},
Media::{Audio::CoreAudio::GUID_NULL, MediaFoundation::*},
System::LibraryLoader::GetModuleHandleW,
UI::WindowsAndMessaging::*,
};
use bindings::bridge::{Result, Error, HRESULT, Interface, Guid};
/// A COM object
struct VideoPlayer(*const VideoPlayerInner);
#[derive(Debug)]
struct VideoPlayerInner {
vtable: *const IMFAsyncCallback_abi,
reference_count: AtomicU32,
window_handle: HWND,
media_session: IMFMediaSession,
}
impl Drop for VideoPlayerInner {
fn drop(&mut self) {
unsafe {
DestroyWindow(self.window_handle);
}
}
}
unsafe extern "system" fn QueryInterface(_this: *mut c_void, _iid: &Guid, _interface: *mut *mut c_void) -> HRESULT {
todo!()
}
unsafe extern "system" fn AddRef(this: *mut c_void) -> u32 {
let inner = &*(this as *mut VideoPlayerInner);
println!("Cloning {:?}", inner);
let refs = inner.reference_count.fetch_add(1, Ordering::Relaxed);
// We want to abort on overflow instead of dropping the value.
// The reference count will never be zero when this is called;
// nevertheless, we insert an abort here to hint LLVM at
// an otherwise missed optimization.
if refs == 0 || refs == u32::MAX {
std::process::abort();
}
refs + 1
}
unsafe extern "system" fn Release(this: *mut c_void) -> u32 {
let inner = &*(this as *mut VideoPlayerInner);
println!("Releasing {:?}", inner);
let refs = inner.reference_count.fetch_sub(1, Ordering::Acquire);
if refs == 1 {
dbg!{{"deallocated it";}}
Box::from_raw(inner as *const VideoPlayerInner as *mut VideoPlayerInner);
}
refs - 1
}
unsafe extern "system" fn GetParameters(
_this: *mut std::ffi::c_void,
_pdwflags: *mut u32,
_pdwqueue: *mut u32,
) -> HRESULT {
E_NOTIMPL
}
unsafe extern "system" fn Invoke(
this: *mut std::ffi::c_void,
pasyncresult: *mut std::ffi::c_void,
) -> HRESULT {
let inner = &*(this as *mut VideoPlayerInner);
let result = core::mem::transmute::<_, IMFAsyncResult>(pasyncresult);
println!("downcasting event");
// This line manages to `Release` `this`,
// presumably because it's stored in `media_session` as a callback.
// However, there is no corresponding `AddRef`
// so the callback is freed prematurely
if let Ok(ev) = inner.media_session.EndGetEvent(result) {
println!("got event");
if let Ok(ty) = ev.GetType() {
println!("got event {}", ty);
if ty != MESessionClosed.0 as u32 {
inner.media_session.BeginGetEvent(core::mem::transmute::<_, IMFAsyncCallback>(this), None).unwrap();
}
}
}
HRESULT(0)
}
const VTABLE: IMFAsyncCallback_abi = IMFAsyncCallback_abi(
QueryInterface,
AddRef,
Release,
GetParameters,
Invoke
);
// impl VideoPlayerInner {
// unsafe fn add_ref
// }
const VIDEO_PLAYER_WINDOW_CLASS_NAME: &[u16] = &utf16_lit::utf16_null!("VIDEOPLAYER");
impl Drop for VideoPlayer {
fn drop(&mut self) {
unsafe {
Release(self.0 as *mut VideoPlayerInner as *mut std::ffi::c_void);
}
}
}
impl VideoPlayer {
pub fn init() -> Result<()> {
unsafe extern "system" fn window_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> isize {
#[link(name = "USER32")]
extern "system" {
fn DefWindowProcW(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> isize;
}
match msg {
WM_DESTROY => {
PostQuitMessage(0);
0
},
msg => DefWindowProcW(hwnd, msg, wparam, lparam),
}
}
// FIXME: `windows`' `LRESULT` definition is incorrect so we have to cast our function pointer
const VIDEOPLAYERPROC: WNDPROC = unsafe { std::mem::transmute::<unsafe extern "system" fn(HWND, u32, WPARAM, LPARAM) -> isize, _>(window_proc) };
let instance = unsafe { GetModuleHandleW(None) };
let atom = unsafe {
RegisterClassExW(&WNDCLASSEXW {
cbSize: core::mem::size_of::<WNDCLASSEXW>() as u32,
style: Default::default(),
lpfnWndProc: Some(VIDEOPLAYERPROC),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: instance,
hIcon: Default::default(),
hCursor: Default::default(),
hbrBackground: Default::default(),
// hbrBackground: COLOR_BACKGROUND,
lpszMenuName: Default::default(),
lpszClassName: PWSTR(VIDEO_PLAYER_WINDOW_CLASS_NAME.as_ptr() as *mut _),
hIconSm: Default::default(),
})
};
if atom == 0 {
Err(HRESULT::from_thread().into())
} else {
Ok(())
}
}
pub fn new() -> Result<Self> {
let window_handle = unsafe {
CreateWindowExW(
WS_EX_OVERLAPPEDWINDOW,
PWSTR(VIDEO_PLAYER_WINDOW_CLASS_NAME.as_ptr() as *mut u16),
PWSTR([0].as_ptr() as *mut u16),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
None,
None,
GetModuleHandleW(None),
core::ptr::null_mut(),
)
};
if window_handle.is_null() {
return Err(HRESULT::from_thread().into());
}
let media_session = unsafe { MFCreateMediaSession(None)? };
let this = Box::into_raw(Box::new(VideoPlayerInner {
vtable: &VTABLE,
reference_count: AtomicU32::new(1),
window_handle,
media_session
}));
unsafe {
println!("Correct: {:?}", *this);
(*this).media_session.BeginGetEvent(core::mem::transmute::<_, IMFAsyncCallback>(this), None)?;
}
Ok(Self(this))
}
fn inner(&self) -> &VideoPlayerInner {
unsafe {
&*self.0
}
}
pub fn handle(&self) -> HWND {
self.inner().window_handle
}
fn open_media_source(path: &Path) -> Result<IMFMediaSource> {
use std::os::windows::ffi::OsStrExt;
let mut wide_chars = path.as_os_str().encode_wide().collect::<Vec<_>>();
// Add null terminator
wide_chars.push(0);
let file = unsafe {
// File is released at end of scope
MFCreateFile(
MF_ACCESSMODE_READ,
MF_OPENMODE_FAIL_IF_NOT_EXIST,
MF_FILEFLAGS_NONE,
PWSTR(wide_chars.as_mut_ptr())
)?
};
let source_resolver = unsafe {
MFCreateSourceResolver()?
};
let mut object_type = Default::default();
let mut source = None;
unsafe {
source_resolver.CreateObjectFromByteStream(
&file,
PWSTR(wide_chars.as_mut_ptr()),
MF_RESOLUTION_MEDIASOURCE.0,
None,
&mut object_type,
&mut source
)?;
}
source
.ok_or_else(|| Error::new(E_UNEXPECTED, "`IMFSourceResolver::CreateObjectFromByteStream` did not return a a media source"))?
.cast()
}
pub fn play_file(&self, path: &Path) -> Result<()> {
let topology = unsafe { MFCreateTopology()? };
let source = Self::open_media_source(path)?;
let descriptor = unsafe {
source.CreatePresentationDescriptor()?
};
for i in 0..unsafe { descriptor.GetStreamDescriptorCount()? } {
let mut selected = false.into();
let mut stream = None;
unsafe {
descriptor.GetStreamDescriptorByIndex(i, &mut selected, &mut stream)?;
}
let stream = stream
.ok_or_else(|| Error::new(E_UNEXPECTED, "`IMFPresentationDescriptor::GetStreamDescriptorByIndex` did not return a stream"))?;
if selected == true {
let handler = unsafe { stream.GetMediaTypeHandler()? };
let major_type = unsafe { handler.GetMajorType()? };
let sink_activate = if major_type == MFMediaType_Audio {
unsafe { MFCreateAudioRendererActivate()? }
} else if major_type == MFMediaType_Video {
unsafe { MFCreateVideoRendererActivate(self.inner().window_handle)? }
} else {
unimplemented!("weird mime type {:?}", major_type)
};
let source_node = unsafe { MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE)? };
unsafe {
source_node.SetUnknown(&MF_TOPONODE_SOURCE, &source)?;
source_node.SetUnknown(&MF_TOPONODE_PRESENTATION_DESCRIPTOR, &descriptor)?;
source_node.SetUnknown(&MF_TOPONODE_STREAM_DESCRIPTOR, &stream)?;
}
unsafe {
topology.AddNode(&source_node)?;
}
let output_node = unsafe { MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE)? };
unsafe {
output_node.SetObject(sink_activate)?;
output_node.SetUINT32(&MF_TOPONODE_STREAMID, 0)?;
output_node.SetUINT32(&MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, 0)?;
}
unsafe {
topology.AddNode(&output_node)?;
}
unsafe {
source_node.ConnectOutput(0, &output_node, 0)?;
}
}
}
let media_session = &self.inner().media_session;
unsafe {
media_session.SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE.0 as _, topology)?;
}
let prop_variant = unsafe { core::mem::zeroed() };
unsafe {
media_session.Start(&GUID_NULL, &prop_variant)
}
}
}
fn main() -> Result<()> {
let path = std::env::args_os().nth(1).unwrap();
unsafe {
// TODO: Version must align with `windows`
MFStartup(0x0270, MFSTARTUP_NOSOCKET)?;
}
VideoPlayer::init()?;
let player = VideoPlayer::new()?;
player.play_file(path.as_ref())?;
unsafe {
ShowWindow(player.handle(), SW_SHOWNORMAL);
}
let mut message = core::mem::MaybeUninit::uninit();
loop {
match unsafe { GetMessageW(message.as_mut_ptr(), None, 0, 0).0 } {
-1 => return Err(HRESULT::from_thread().into()),
0 => return Ok(()),
_ => {
unsafe {
TranslateMessage(message.as_ptr());
DispatchMessageW(message.as_ptr());
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment