audio support
This commit is contained in:
parent
e183627018
commit
043806c713
|
@ -10,10 +10,11 @@ failure = "^0.1"
|
|||
libloading = "^0.5"
|
||||
num_enum = "^0.4"
|
||||
structopt = "^0.3"
|
||||
sdl2 = { version = "*", optional = true, features = ["unsafe_textures"] }
|
||||
sdl2 = { version = "^0.32", optional = true }
|
||||
crossbeam-channel = { version = "^0.4", optional = true }
|
||||
|
||||
[features]
|
||||
with-sdl2 = ["sdl2"]
|
||||
with-sdl2 = ["sdl2", "crossbeam-channel"]
|
||||
|
||||
[[bin]]
|
||||
name = "example"
|
||||
|
|
|
@ -1,31 +1,41 @@
|
|||
extern crate crossbeam_channel;
|
||||
extern crate rustro;
|
||||
extern crate sdl2;
|
||||
|
||||
use rustro::retro;
|
||||
use rustro::retro::ffi::GameGeometry;
|
||||
use rustro::retro::ffi::SystemAvInfo;
|
||||
use rustro::retro::ffi::{GameGeometry, SystemAvInfo};
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
use std::path::{PathBuf, Path};
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use structopt::StructOpt;
|
||||
|
||||
use sdl2::render::{WindowCanvas, Texture, TextureCreator};
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::audio::{AudioCallback, AudioFormat, AudioSpec, AudioSpecDesired, AudioDevice};
|
||||
use sdl2::event::Event;
|
||||
use sdl2::keyboard::Keycode;
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::{TextureCreator, WindowCanvas};
|
||||
use sdl2::video::WindowContext;
|
||||
|
||||
struct MyEmulator {
|
||||
retro: retro::wrapper::LibretroWrapper,
|
||||
av_info: SystemAvInfo,
|
||||
|
||||
sdl_context: sdl2::Sdl,
|
||||
|
||||
// video bits
|
||||
canvas: WindowCanvas,
|
||||
pixel_format: sdl2::pixels::PixelFormatEnum,
|
||||
texture: Option<Texture>,
|
||||
texture_creator: TextureCreator<WindowContext>,
|
||||
|
||||
// audio bits
|
||||
audio_buffer: Vec<i16>,
|
||||
audio_spec: AudioSpec,
|
||||
audio_device: AudioDevice<MySdlAudio>,
|
||||
audio_sender: crossbeam_channel::Sender<Vec<i16>>,
|
||||
}
|
||||
|
||||
impl MyEmulator {
|
||||
|
@ -34,40 +44,143 @@ impl MyEmulator {
|
|||
let raw_retro = retro::loading::LibretroApi::from_library(lib).unwrap();
|
||||
let retro = retro::wrapper::LibretroWrapper::from(raw_retro);
|
||||
println!("api version: {}", retro.as_ref().api_version());
|
||||
println!("name: {}", unsafe { CStr::from_ptr(retro.as_ref().get_system_info().library_name) }.to_string_lossy());
|
||||
println!(
|
||||
"name: {}",
|
||||
unsafe { CStr::from_ptr(retro.as_ref().get_system_info().library_name) }
|
||||
.to_string_lossy()
|
||||
);
|
||||
|
||||
let av_info = retro.as_ref().get_system_av_info();
|
||||
|
||||
let pixel_format = sdl2::pixels::PixelFormatEnum::ABGR1555;
|
||||
let mut emu = MyEmulator { retro, av_info, pixel_format, texture: None };
|
||||
let mut pin_emu = Box::pin(emu);
|
||||
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
|
||||
let (width, height) = (av_info.geometry.base_width, av_info.geometry.base_height);
|
||||
let window = sdl_context
|
||||
.video()
|
||||
.unwrap()
|
||||
.window("rust libretro", width, height)
|
||||
.opengl()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let canvas = window.into_canvas().build().unwrap();
|
||||
let texture_creator = canvas.texture_creator();
|
||||
|
||||
let (audio_sender, audio_receiver) = crossbeam_channel::bounded(2);
|
||||
|
||||
let audio = sdl_context.audio().unwrap();
|
||||
let desired_spec = AudioSpecDesired {
|
||||
freq: Some(av_info.timing.sample_rate.round() as i32),
|
||||
channels: Some(2),
|
||||
samples: None,
|
||||
};
|
||||
let mut audio_spec = None;
|
||||
let audio_device = audio
|
||||
.open_playback(None, &desired_spec, |spec| {
|
||||
println!("audio format {:?}", spec.format);
|
||||
audio_spec = Some(spec.clone());
|
||||
MySdlAudio {
|
||||
audio_spec: spec,
|
||||
audio_receiver,
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let emu = MyEmulator {
|
||||
retro,
|
||||
av_info,
|
||||
sdl_context,
|
||||
canvas,
|
||||
pixel_format,
|
||||
texture_creator,
|
||||
audio_buffer: Default::default(),
|
||||
audio_spec: audio_spec.unwrap(),
|
||||
audio_device,
|
||||
audio_sender,
|
||||
};
|
||||
let mut pin_emu = Box::pin(emu);
|
||||
retro::wrapper::register_handler(pin_emu.as_mut());
|
||||
pin_emu.retro.as_ref().init();
|
||||
pin_emu
|
||||
}
|
||||
|
||||
pub fn run(&self) {
|
||||
self.retro.as_ref().run();
|
||||
}
|
||||
pub fn run(&mut self) {
|
||||
self.audio_device.resume();
|
||||
let mut event_pump = self.sdl_context.event_pump().unwrap();
|
||||
'running: loop {
|
||||
let frame_begin = Instant::now();
|
||||
|
||||
pub fn base_dimensions(&self) -> (u32, u32) {
|
||||
(self.av_info.geometry.base_width, self.av_info.geometry.base_height)
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. }
|
||||
| Event::KeyDown {
|
||||
keycode: Some(Keycode::Escape),
|
||||
..
|
||||
} => break 'running,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// The rest of the game loop goes here...
|
||||
self.retro.as_ref().run();
|
||||
self.canvas.present();
|
||||
|
||||
Duration::from_secs_f64(1.0 / self.av_info.timing.fps)
|
||||
.checked_sub(frame_begin.elapsed())
|
||||
.map(std::thread::sleep);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_game(&self, rom: impl AsRef<Path>) {
|
||||
self.retro.as_ref().load_game(Some(rom.as_ref()), None, None);
|
||||
let mut data = None;
|
||||
let mut v = Vec::new();
|
||||
if let Ok(mut f) = std::fs::File::open(rom.as_ref()) {
|
||||
if f.read_to_end(&mut v).is_ok() {
|
||||
data = Some(v.as_ref());
|
||||
}
|
||||
}
|
||||
self.retro
|
||||
.as_ref()
|
||||
.load_game(Some(rom.as_ref()), data, None)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn send_audio_samples(&mut self) {
|
||||
let stereo_samples = self.audio_spec.samples as usize * 2;
|
||||
while self.audio_buffer.len() >= stereo_samples {
|
||||
let remainder = self.audio_buffer.split_off(stereo_samples);
|
||||
let msg = std::mem::replace(&mut self.audio_buffer, remainder);
|
||||
let _ = self.audio_sender.try_send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl retro::convert::Handler for MyEmulator {
|
||||
impl retro::wrapper::Handler for MyEmulator {
|
||||
fn video_refresh(&mut self, data: &[u8], width: u32, height: u32, pitch: u32) {
|
||||
let rect = Rect::new(0, 0, width, height);
|
||||
if let Some(tex) = self.texture.as_mut() {
|
||||
tex.update(rect, data, pitch as usize).unwrap();
|
||||
|
||||
if let Ok(mut tex) =
|
||||
self.texture_creator
|
||||
.create_texture_static(self.pixel_format, width, height)
|
||||
{
|
||||
if tex.update(rect, data, pitch as usize).is_ok() {
|
||||
self.canvas.copy(&tex, None, None).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn audio_sample(&mut self, left: i16, right: i16) {
|
||||
self.audio_buffer.push(left);
|
||||
self.audio_buffer.push(right);
|
||||
self.send_audio_samples()
|
||||
}
|
||||
|
||||
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize {
|
||||
self.audio_buffer.extend(stereo_pcm);
|
||||
self.send_audio_samples();
|
||||
stereo_pcm.len()
|
||||
}
|
||||
|
||||
fn set_pixel_format(&mut self, pix_fmt: retro::ffi::PixelFormat) -> bool {
|
||||
self.pixel_format = match pix_fmt {
|
||||
retro::ffi::PixelFormat::ARGB1555 => sdl2::pixels::PixelFormatEnum::RGB555,
|
||||
|
@ -76,12 +189,34 @@ impl retro::convert::Handler for MyEmulator {
|
|||
};
|
||||
true
|
||||
}
|
||||
|
||||
fn set_system_av_info(&mut self, av_info: SystemAvInfo) -> bool {
|
||||
self.av_info = av_info;
|
||||
true
|
||||
}
|
||||
|
||||
fn set_geometry(&mut self, geom: GameGeometry) -> bool {
|
||||
self.av_info.geometry = geom;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MyEmulator {
|
||||
fn drop(&mut self) {
|
||||
if let Some(x) = self.texture.take() {
|
||||
unsafe { x.destroy(); }
|
||||
struct MySdlAudio {
|
||||
audio_spec: AudioSpec,
|
||||
audio_receiver: crossbeam_channel::Receiver<Vec<i16>>,
|
||||
}
|
||||
|
||||
impl AudioCallback for MySdlAudio {
|
||||
type Channel = i16;
|
||||
|
||||
fn callback(&mut self, out: &mut [Self::Channel]) {
|
||||
match self.audio_spec.format {
|
||||
AudioFormat::S16LSB | AudioFormat::U16LSB => {
|
||||
if let Ok(samples) = self.audio_receiver.recv() {
|
||||
out.copy_from_slice(&samples[..out.len()]);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,43 +225,8 @@ pub fn main() -> failure::Fallible<()> {
|
|||
let opt: Opt = Opt::from_args();
|
||||
let mut emu = MyEmulator::with_core(&opt.core);
|
||||
emu.load_game(&opt.rom);
|
||||
emu.run();
|
||||
|
||||
let sdl_context = sdl2::init().map_err(failure::err_msg)?;
|
||||
let video_subsystem = sdl_context.video().map_err(failure::err_msg)?;
|
||||
|
||||
let (width, height) = emu.base_dimensions();
|
||||
let window = video_subsystem.window("rust libretro", width, height)
|
||||
.opengl()
|
||||
.build()?;
|
||||
|
||||
let mut canvas = window.into_canvas().build()?;
|
||||
let texture_creator = canvas.texture_creator();
|
||||
emu.texture = texture_creator.create_texture_streaming(emu.pixel_format, width, height).ok();
|
||||
|
||||
let target_frame_time = Duration::from_secs_f64(1.0 / emu.av_info.timing.fps);
|
||||
|
||||
let mut event_pump = sdl_context.event_pump().map_err(failure::err_msg)?;
|
||||
'running: loop {
|
||||
let frame_begin = Instant::now();
|
||||
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit {..}
|
||||
| Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
|
||||
break 'running
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// The rest of the game loop goes here...
|
||||
emu.run();
|
||||
canvas.clear();
|
||||
canvas.copy(emu.texture.as_ref().unwrap(), None, Some(Rect::new(0, 0, width, height))).map_err(failure::err_msg)?;
|
||||
canvas.present();
|
||||
|
||||
target_frame_time.checked_sub(frame_begin.elapsed()).map(sleep);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -139,3 +239,4 @@ struct Opt {
|
|||
#[structopt(short, long, parse(from_os_str))]
|
||||
rom: PathBuf,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::convert::{TryFrom, TryInto};
|
||||
use std::os::raw::c_uint;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use num_enum::{TryFromPrimitive, IntoPrimitive};
|
||||
|
||||
|
@ -188,52 +187,3 @@ pub enum EnvCmd {
|
|||
GetLanguage = ENVIRONMENT_GET_LANGUAGE,
|
||||
// SetSerializationQuirks = ENVIRONMENT_SET_SERIALIZATION_QUIRKS,
|
||||
}
|
||||
|
||||
pub trait Handler: Unpin + 'static {
|
||||
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint) {}
|
||||
fn audio_sample(&mut self, left: i16, right: i16) {}
|
||||
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize { stereo_pcm.len() }
|
||||
fn input_poll(&mut self) {}
|
||||
fn input_state(&mut self, port: u32, device: Input, index: DeviceIndex) -> i16 { 0 }
|
||||
|
||||
// -- environment callbacks --
|
||||
fn set_rotation(&mut self, rotation: EnvRotation) -> bool { false }
|
||||
fn get_overscan(&mut self) -> Option<bool> { None }
|
||||
fn get_can_dupe(&mut self) -> Option<bool> { None }
|
||||
fn set_message(&mut self, message: Message) -> bool { false }
|
||||
fn shutdown(&mut self) -> bool { false }
|
||||
fn set_performance_level(&mut self, level: c_uint) -> bool { false }
|
||||
fn get_system_directory(&mut self) -> Option<PathBuf> { None }
|
||||
fn set_pixel_format(&mut self, format: PixelFormat) -> bool { false }
|
||||
fn set_input_descriptors(&mut self, input_descriptors: &[InputDescriptor]) -> bool { false }
|
||||
fn set_keyboard_callback(&mut self, cb: KeyboardCallback) -> bool { false }
|
||||
fn set_disk_control_interface(&mut self, cb: DiskControlCallback) -> bool { false }
|
||||
fn set_hw_render(&mut self, hw_render_callback: HwRenderCallback) -> bool { false }
|
||||
fn get_variable(&mut self) -> Option<EnvVariable> { None }
|
||||
fn set_variables(&mut self, variables: &[EnvVariable]) -> bool { false }
|
||||
fn get_variable_update(&mut self) -> Option<bool> { None }
|
||||
fn set_support_no_game(&mut self, supports_no_game: bool) -> bool { false }
|
||||
fn get_libretro_path(&mut self) -> Option<PathBuf> { None }
|
||||
fn set_frame_time_callback(&mut self, cb: FrameTimeCallback) -> bool { false }
|
||||
fn set_audio_callback(&mut self, audio_callback: AudioCallback) -> bool { false }
|
||||
fn get_rumble_interface(&mut self) -> Option<RumbleInterface> { None }
|
||||
fn get_input_device_capabilities(&mut self) -> Option<u64> { None }
|
||||
fn get_sensor_interface(&mut self) -> Option<SensorInterface> { None }
|
||||
fn get_camera_interface(&mut self) -> Option<CameraCallback> { None }
|
||||
fn get_log_interface(&mut self) -> Option<LogCallback> { None }
|
||||
fn get_perf_interface(&mut self) -> Option<PerfCallback> { None }
|
||||
fn get_location_interface(&mut self) -> Option<LocationCallback> { None }
|
||||
fn get_core_assets_directory(&mut self) -> Option<PathBuf> { None }
|
||||
fn get_save_directory(&mut self) -> Option<PathBuf> { None }
|
||||
fn set_system_av_info(&mut self, system_av_info: SystemAvInfo) -> bool { false }
|
||||
fn set_proc_address_callback(&mut self, cb: GetProcAddressInterface) -> bool { false }
|
||||
fn set_subsystem_info(&mut self, subsystem_info: SubsystemInfo) -> bool { false }
|
||||
fn set_controller_info(&mut self, controller_info: ControllerInfo) -> bool { false }
|
||||
fn set_memory_maps(&mut self, memory_map: MemoryMap) -> bool { false }
|
||||
fn set_geometry(&mut self, game_geometry: GameGeometry) -> bool { false }
|
||||
fn get_username(&mut self) -> Option<String> { None }
|
||||
fn get_language(&mut self) -> Option<Language> { None }
|
||||
// fn set_serialization_quirks(&mut self, quirks: &mut u64) -> bool { false }
|
||||
}
|
||||
|
||||
// impl<T: Handler> Unpin for T {}
|
|
@ -7,7 +7,7 @@ use libloading;
|
|||
use super::ffi::*;
|
||||
|
||||
pub struct LibretroApi {
|
||||
lib: libloading::Library, // for tying our lifetime to its own
|
||||
_lib: libloading::Library, // for tying our lifetime to its own
|
||||
pub core_api: CoreAPI,
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ impl LibretroApi {
|
|||
retro_get_memory_data: *lib.get(b"retro_get_memory_data")?,
|
||||
retro_get_memory_size: *lib.get(b"retro_get_memory_size")?,
|
||||
};
|
||||
Ok(LibretroApi { lib, core_api })
|
||||
Ok(LibretroApi { _lib: lib, core_api })
|
||||
}
|
||||
}
|
||||
/// set_environment() must be called before init().
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub mod convert;
|
||||
pub mod constants;
|
||||
#[allow(non_camel_case_types, non_upper_case_globals, non_snake_case, dead_code)]
|
||||
pub mod ffi;
|
||||
pub mod loading;
|
||||
|
|
|
@ -4,20 +4,67 @@ use core::slice::from_raw_parts;
|
|||
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::{c_uint, c_char};
|
||||
use std::ops::DerefMut;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
|
||||
use libretro_sys::{Message, PixelFormat, SystemAvInfo, GameGeometry};
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
use super::convert::*;
|
||||
use super::constants::*;
|
||||
use super::ffi::*;
|
||||
use super::loading::*;
|
||||
|
||||
static mut CB_SINGLETON: StaticCallbacks = StaticCallbacks {
|
||||
handler: None,
|
||||
};
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait Handler: Unpin + 'static {
|
||||
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint) {}
|
||||
fn audio_sample(&mut self, left: i16, right: i16) {}
|
||||
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize { stereo_pcm.len() }
|
||||
fn input_poll(&mut self) {}
|
||||
fn input_state(&mut self, port: u32, device: Input, index: DeviceIndex) -> i16 { 0 }
|
||||
|
||||
// -- environment callbacks --
|
||||
fn set_rotation(&mut self, rotation: EnvRotation) -> bool { false }
|
||||
fn get_overscan(&mut self) -> Option<bool> { None }
|
||||
fn get_can_dupe(&mut self) -> Option<bool> { None }
|
||||
fn set_message(&mut self, message: Message) -> bool { false }
|
||||
fn shutdown(&mut self) -> bool { false }
|
||||
fn set_performance_level(&mut self, level: c_uint) -> bool { false }
|
||||
fn get_system_directory(&mut self) -> Option<PathBuf> { None }
|
||||
fn set_pixel_format(&mut self, format: PixelFormat) -> bool { false }
|
||||
fn set_input_descriptors(&mut self, input_descriptors: &[InputDescriptor]) -> bool { false }
|
||||
fn set_keyboard_callback(&mut self, cb: KeyboardCallback) -> bool { false }
|
||||
fn set_disk_control_interface(&mut self, cb: DiskControlCallback) -> bool { false }
|
||||
fn set_hw_render(&mut self, hw_render_callback: HwRenderCallback) -> bool { false }
|
||||
fn get_variable(&mut self) -> Option<EnvVariable> { None }
|
||||
fn set_variables(&mut self, variables: &[EnvVariable]) -> bool { false }
|
||||
fn get_variable_update(&mut self) -> Option<bool> { None }
|
||||
fn set_support_no_game(&mut self, supports_no_game: bool) -> bool { false }
|
||||
fn get_libretro_path(&mut self) -> Option<PathBuf> { None }
|
||||
fn set_frame_time_callback(&mut self, cb: FrameTimeCallback) -> bool { false }
|
||||
fn set_audio_callback(&mut self, audio_callback: AudioCallback) -> bool { false }
|
||||
fn get_rumble_interface(&mut self) -> Option<RumbleInterface> { None }
|
||||
fn get_input_device_capabilities(&mut self) -> Option<u64> { None }
|
||||
fn get_sensor_interface(&mut self) -> Option<SensorInterface> { None }
|
||||
fn get_camera_interface(&mut self) -> Option<CameraCallback> { None }
|
||||
fn get_log_interface(&mut self) -> Option<LogCallback> { None }
|
||||
fn get_perf_interface(&mut self) -> Option<PerfCallback> { None }
|
||||
fn get_location_interface(&mut self) -> Option<LocationCallback> { None }
|
||||
fn get_core_assets_directory(&mut self) -> Option<PathBuf> { None }
|
||||
fn get_save_directory(&mut self) -> Option<PathBuf> { None }
|
||||
fn set_system_av_info(&mut self, system_av_info: SystemAvInfo) -> bool { false }
|
||||
fn set_proc_address_callback(&mut self, cb: GetProcAddressInterface) -> bool { false }
|
||||
fn set_subsystem_info(&mut self, subsystem_info: SubsystemInfo) -> bool { false }
|
||||
fn set_controller_info(&mut self, controller_info: ControllerInfo) -> bool { false }
|
||||
fn set_memory_maps(&mut self, memory_map: MemoryMap) -> bool { false }
|
||||
fn set_geometry(&mut self, game_geometry: GameGeometry) -> bool { false }
|
||||
fn get_username(&mut self) -> Option<String> { None }
|
||||
fn get_language(&mut self) -> Option<Language> { None }
|
||||
// fn set_serialization_quirks(&mut self, quirks: &mut u64) -> bool { false }
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StaticCallbacks {
|
||||
handler: Option<Pin<&'static mut dyn Handler>>,
|
||||
|
@ -48,7 +95,7 @@ impl StaticCallbacks {
|
|||
T::try_from_primitive(number).ok()
|
||||
}
|
||||
fn environment_cb_inner(cmd: u32, data: *mut c_void) -> Option<bool> {
|
||||
let mut handler = unsafe { CB_SINGLETON.handler.as_mut() }?;
|
||||
let handler = unsafe { CB_SINGLETON.handler.as_mut() }?;
|
||||
match cmd.try_into().ok()? {
|
||||
EnvCmd::SetRotation => handler.set_rotation(Self::enum_from_void(data)?),
|
||||
EnvCmd::GetOverscan => Self::clone_into_void(data, &handler.get_overscan()?)?,
|
||||
|
@ -119,7 +166,11 @@ impl StaticCallbacks {
|
|||
match CB_SINGLETON.handler.as_mut() {
|
||||
Some(cb) => match data.is_null() {
|
||||
true => 0,
|
||||
false => cb.audio_sample_batch(from_raw_parts(data, frames)),
|
||||
false => {
|
||||
let len = frames * 2; // stereo
|
||||
let result = cb.audio_sample_batch(from_raw_parts(data, len));
|
||||
result / 2
|
||||
},
|
||||
},
|
||||
None => 0,
|
||||
}
|
||||
|
@ -176,7 +227,7 @@ impl Drop for LibretroWrapper {
|
|||
// the library wrapper itself we're good (we wipe our 'static references on drop() too)
|
||||
pub fn register_handler(handler: Pin<&'_ mut (dyn Handler + '_)>) {
|
||||
unsafe {
|
||||
let mut ptr = handler.get_unchecked_mut() as *mut dyn Handler;
|
||||
let ptr = handler.get_unchecked_mut() as *mut dyn Handler;
|
||||
CB_SINGLETON.handler.replace(Pin::new_unchecked(ptr.as_mut().unwrap()));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue