bennetthardwick / rust-obs-plugins

A safe wrapper around the OBS API, useful for creating OBS sources, filters and effects.
GNU General Public License v2.0
186 stars 34 forks source link

Input source example to render frames #47

Closed ShayBox closed 8 months ago

ShayBox commented 8 months ago

I'm trying to write a plugin to render frames with nvfbc, but I can't figure out how to render frames with obs-wrapper.

I found VideoRenderSource and VideoTickSource, but I can't figure out how to actually render frames, GlobalContext and VideoRenderContext don't appear to have any fields or methods.

use obs_wrapper::{
    obs_register_module,
    obs_string,
    prelude::*,
    source::{
        GetNameSource,
        SourceContext,
        SourceType,
        Sourceable,
        VideoRenderSource,
        VideoTickSource,
    },
};

struct NvidiaFrameBufferCaptureSource {
    source: SourceContext,
}

struct NvidiaFrameBufferCaptureModule {
    context: ModuleContext,
}

impl Sourceable for NvidiaFrameBufferCaptureSource {
    fn get_id() -> ObsString {
        obs_string!("nvfbc")
    }

    fn get_type() -> SourceType {
        SourceType::INPUT
    }

    fn create(_create: &mut CreatableSourceContext<Self>, source: SourceContext) -> Self {
        Self { source }
    }
}

impl GetNameSource for NvidiaFrameBufferCaptureSource {
    fn get_name() -> ObsString {
        obs_string!("NvFBC")
    }
}

impl VideoRenderSource for NvidiaFrameBufferCaptureSource {
    fn video_render(&mut self, _context: &mut GlobalContext, _render: &mut VideoRenderContext) {
        todo!()
    }
}

impl VideoTickSource for NvidiaFrameBufferCaptureSource {
    fn video_tick(&mut self, _seconds: f32) {
        todo!()
    }
}

impl Module for NvidiaFrameBufferCaptureModule {
    fn new(context: ModuleContext) -> Self {
        Self { context }
    }

    fn get_ctx(&self) -> &ModuleContext {
        &self.context
    }

    fn load(&mut self, load_context: &mut LoadContext) -> bool {
        let source = load_context
            .create_source_builder::<NvidiaFrameBufferCaptureSource>()
            .enable_get_name()
            .enable_video_render()
            .enable_video_tick()
            .build();

        load_context.register_source(source);

        true
    }

    fn description() -> ObsString {
        obs_string!(env!("CARGO_PKG_DESCRIPTION"))
    }

    fn name() -> ObsString {
        obs_string!(env!("CARGO_PKG_NAME"))
    }

    fn author() -> ObsString {
        obs_string!(env!("CARGO_PKG_AUTHORS"))
    }
}

obs_register_module!(NvidiaFrameBufferCaptureModule);
ShayBox commented 8 months ago

I figured out how to do it using some projects I found

https://github.com/P1n3appl3/obs-livesplit/blob/main/src/lib.rs https://github.com/P1n3appl3/obs-gamepad/blob/main/src/lib.rs https://github.com/agrif/ds1054z-source/blob/master/src/source.rs https://github.com/chengyuhui/obs-subtitle/blob/master/src/lib.rs

But, It appears NvFBC was deprecated for Windows 10, though it still works it is outdated, and the nvfbc rust bindgen library only targets Linux which is still supported. Feel free to use this on Linux.

use nvfbc::{system::CaptureMethod, BufferFormat, SystemCapturer};
use obs_wrapper::{
    graphics::{GraphicsColorFormat, GraphicsTexture},
    obs_register_module,
    obs_string,
    prelude::*,
    source::{
        GetNameSource,
        SourceContext,
        SourceType,
        Sourceable,
        VideoRenderSource,
        VideoTickSource,
    },
};

struct NvidiaFrameBufferCaptureSource {
    source:   SourceContext,
    texture:  GraphicsTexture,
    capturer: SystemCapturer,
}

struct NvidiaFrameBufferCaptureModule {
    context: ModuleContext,
}

impl Sourceable for NvidiaFrameBufferCaptureSource {
    fn get_id() -> ObsString {
        obs_string!("nvfbc")
    }

    fn get_type() -> SourceType {
        SourceType::INPUT
    }

    fn create(_create: &mut CreatableSourceContext<Self>, source: SourceContext) -> Self {
        let mut capturer = SystemCapturer::new().expect("Failed to create SystemCapturer");

        capturer
            .start(BufferFormat::Rgb, 30)
            .expect("Failed to start SystemCapturer");

        let frame_info = capturer
            .next_frame(CaptureMethod::Blocking)
            .expect("Failed to get SystemFrameInfo");

        let texture = GraphicsTexture::new(
            frame_info.width,
            frame_info.height,
            GraphicsColorFormat::RGBA,
        );

        Self {
            source,
            texture,
            capturer,
        }
    }
}

impl GetNameSource for NvidiaFrameBufferCaptureSource {
    fn get_name() -> ObsString {
        obs_string!("NvFBC")
    }
}

impl VideoRenderSource for NvidiaFrameBufferCaptureSource {
    fn video_render(&mut self, _context: &mut GlobalContext, _render: &mut VideoRenderContext) {
        let frame_info = self
            .capturer
            .next_frame(CaptureMethod::Blocking)
            .expect("Failed to get SystemFrameInfo");

        self.texture
            .set_image(frame_info.buffer, frame_info.width * 4, false);
        self.texture.draw(0, 0, 2560, 1440, false);
    }
}

impl Module for NvidiaFrameBufferCaptureModule {
    fn new(context: ModuleContext) -> Self {
        Self { context }
    }

    fn get_ctx(&self) -> &ModuleContext {
        &self.context
    }

    fn load(&mut self, load_context: &mut LoadContext) -> bool {
        let source = load_context
            .create_source_builder::<NvidiaFrameBufferCaptureSource>()
            .enable_get_name()
            .enable_video_render()
            .build();

        load_context.register_source(source);

        true
    }

    fn description() -> ObsString {
        obs_string!(env!("CARGO_PKG_DESCRIPTION"))
    }

    fn name() -> ObsString {
        obs_string!(env!("CARGO_PKG_NAME"))
    }

    fn author() -> ObsString {
        obs_string!(env!("CARGO_PKG_AUTHORS"))
    }
}

obs_register_module!(NvidiaFrameBufferCaptureModule);
bennetthardwick commented 8 months ago

Thanks @ShayBox!