| 1 | extern crate sdl2; |
| 2 | extern crate nihav_core; |
| 3 | extern crate nihav_registry; |
| 4 | extern crate nihav_allstuff; |
| 5 | |
| 6 | use std::env; |
| 7 | use std::fs::File; |
| 8 | use std::io::Write; |
| 9 | use std::path::Path; |
| 10 | use std::time::{Duration, Instant}; |
| 11 | use std::thread; |
| 12 | use std::sync::atomic::{AtomicU8, Ordering}; |
| 13 | |
| 14 | use sdl2::event::{Event, WindowEvent}; |
| 15 | use sdl2::keyboard::{Keycode, Mod}; |
| 16 | use sdl2::mouse::{MouseButton, MouseWheelDirection}; |
| 17 | use sdl2::render::{Canvas, Texture, TextureCreator}; |
| 18 | use sdl2::pixels::PixelFormatEnum; |
| 19 | use sdl2::video::{Window, WindowContext}; |
| 20 | |
| 21 | use nihav_registry::detect; |
| 22 | use nihav_core::frame::*; |
| 23 | use nihav_core::io::byteio::{FileReader, ByteReader}; |
| 24 | use nihav_core::reorder::*; |
| 25 | use nihav_core::codecs::*; |
| 26 | use nihav_core::demuxers::*; |
| 27 | use nihav_registry::register::*; |
| 28 | use nihav_allstuff::*; |
| 29 | |
| 30 | #[cfg(feature="hwaccel")] |
| 31 | use hwdec_vaapi::*; |
| 32 | |
| 33 | mod audiodec; |
| 34 | use audiodec::*; |
| 35 | mod videodec; |
| 36 | use videodec::*; |
| 37 | mod osd; |
| 38 | use osd::*; |
| 39 | |
| 40 | #[repr(u8)] |
| 41 | #[derive(Clone,Copy,Debug,PartialEq,Default)] |
| 42 | enum DecodingState { |
| 43 | #[default] |
| 44 | Normal, |
| 45 | Waiting, |
| 46 | Flush, |
| 47 | Prefetch, |
| 48 | Error, |
| 49 | End, |
| 50 | } |
| 51 | |
| 52 | impl From<u8> for DecodingState { |
| 53 | fn from(val: u8) -> Self { |
| 54 | match val { |
| 55 | 0 => DecodingState::Normal, |
| 56 | 1 => DecodingState::Waiting, |
| 57 | 2 => DecodingState::Flush, |
| 58 | 3 => DecodingState::Prefetch, |
| 59 | 4 => DecodingState::End, |
| 60 | _ => DecodingState::Error, |
| 61 | } |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | struct DecoderState { |
| 66 | state: AtomicU8 |
| 67 | } |
| 68 | |
| 69 | impl DecoderState { |
| 70 | const fn new() -> Self { |
| 71 | Self { |
| 72 | state: AtomicU8::new(DecodingState::Normal as u8) |
| 73 | } |
| 74 | } |
| 75 | fn set_state(&self, state: DecodingState) { |
| 76 | self.state.store(state as u8, Ordering::Release); |
| 77 | } |
| 78 | fn get_state(&self) -> DecodingState { |
| 79 | self.state.load(Ordering::Acquire).into() |
| 80 | } |
| 81 | fn is_flushing(&self) -> bool { |
| 82 | matches!(self.get_state(), DecodingState::Flush | DecodingState::Error) |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | #[cfg(feature="debug")] |
| 87 | macro_rules! debug_log { |
| 88 | ($log: expr; $blk: block) => { |
| 89 | $log.logfile.write($blk.as_bytes()).unwrap(); |
| 90 | $log.logfile.write(b"\n").unwrap(); |
| 91 | }; |
| 92 | } |
| 93 | #[cfg(not(feature="debug"))] |
| 94 | macro_rules! debug_log { |
| 95 | ($log: expr; $blk: block) => {}; |
| 96 | } |
| 97 | |
| 98 | enum ScaleSize { |
| 99 | Auto, |
| 100 | Times(f32), |
| 101 | Fixed(usize, usize) |
| 102 | } |
| 103 | |
| 104 | impl FromStr for ScaleSize { |
| 105 | type Err = (); |
| 106 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 107 | if matches!(s, "" | "auto") { |
| 108 | Ok(ScaleSize::Auto) |
| 109 | } else if s.ends_with('x') || s.ends_with('X') { |
| 110 | let factor = s[..s.len() - 1].parse::<f32>().map_err(|_| ())?; |
| 111 | if factor > 0.0 { |
| 112 | Ok(ScaleSize::Times(factor)) |
| 113 | } else { |
| 114 | Err(()) |
| 115 | } |
| 116 | } else if s.contains('x') | s.contains('X') { |
| 117 | let mut dims = if s.contains('x') { s.split('x') } else { s.split('X') }; |
| 118 | let w = dims.next().unwrap(); |
| 119 | let h = dims.next().unwrap(); |
| 120 | let width = w.parse::<usize>().map_err(|_| ())?; |
| 121 | let height = h.parse::<usize>().map_err(|_| ())?; |
| 122 | if width > 0 && height > 0 { |
| 123 | Ok(ScaleSize::Fixed(width, height)) |
| 124 | } else { |
| 125 | Err(()) |
| 126 | } |
| 127 | } else { |
| 128 | Err(()) |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | pub enum PktSendEvent { |
| 134 | Packet(NAPacket), |
| 135 | GetFrames, |
| 136 | Flush, |
| 137 | End, |
| 138 | ImmediateEnd, |
| 139 | HurryUp, |
| 140 | } |
| 141 | |
| 142 | pub enum DecoderType { |
| 143 | Audio(Box<dyn NADecoder + Send>), |
| 144 | Video(Box<dyn NADecoder + Send>, Box<dyn FrameReorderer + Send>), |
| 145 | VideoMT(Box<dyn NADecoderMT + Send>, MTFrameReorderer), |
| 146 | #[cfg(feature="hwaccel")] |
| 147 | VideoHW(Box<dyn HWDecoder + Send>), |
| 148 | } |
| 149 | |
| 150 | pub struct DecoderStuff { |
| 151 | pub dsupp: Box<NADecoderSupport>, |
| 152 | pub dec: DecoderType, |
| 153 | } |
| 154 | |
| 155 | fn format_time(ms: u64) -> String { |
| 156 | let s = ms / 1000; |
| 157 | let ds = (ms % 1000) / 100; |
| 158 | let (min, s) = (s / 60, s % 60); |
| 159 | let (h, min) = (min / 60, min % 60); |
| 160 | if h == 0 { |
| 161 | if min == 0 { |
| 162 | format!("{}.{}", s, ds) |
| 163 | } else { |
| 164 | format!("{}:{:02}.{}", min, s, ds) |
| 165 | } |
| 166 | } else { |
| 167 | format!("{}:{:02}:{:02}.{}", h, min, s, ds) |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | const FRAME_QUEUE_LEN: usize = 25; |
| 172 | const MAX_VOLUME: usize = 200; |
| 173 | |
| 174 | pub type FrameRecord = (NABufferType, u64); |
| 175 | |
| 176 | pub struct TimeKeep { |
| 177 | ref_time: Instant, |
| 178 | ref_ts: u64, |
| 179 | } |
| 180 | |
| 181 | impl TimeKeep { |
| 182 | fn new() -> Self { |
| 183 | Self { |
| 184 | ref_time: Instant::now(), |
| 185 | ref_ts: 0, |
| 186 | } |
| 187 | } |
| 188 | pub fn get_cur_time(&self) -> u64 { |
| 189 | let add = self.ref_time.elapsed().as_millis() as u64; |
| 190 | self.ref_ts + add |
| 191 | } |
| 192 | fn reset_ts(&mut self) { |
| 193 | self.ref_ts = 0; |
| 194 | } |
| 195 | fn reset_all(&mut self, ts: u64) { |
| 196 | self.ref_time = Instant::now(); |
| 197 | self.ref_ts = ts; |
| 198 | } |
| 199 | fn set_ts(&mut self) { |
| 200 | self.ref_ts = self.get_cur_time(); |
| 201 | } |
| 202 | fn set_time(&mut self) { |
| 203 | self.ref_time = Instant::now(); |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | pub struct DispFrame<'a> { |
| 208 | pub ts: u64, |
| 209 | pub is_yuv: bool, |
| 210 | pub valid: bool, |
| 211 | pub rgb_tex: Texture<'a>, |
| 212 | pub yuv_tex: Texture<'a>, |
| 213 | } |
| 214 | |
| 215 | pub struct DispQueue<'a> { |
| 216 | pub pool: Vec<DispFrame<'a>>, |
| 217 | pub first_ts: u64, |
| 218 | pub last_ts: u64, |
| 219 | pub start: usize, |
| 220 | pub end: usize, |
| 221 | pub len: usize, |
| 222 | pub width: usize, |
| 223 | pub height: usize, |
| 224 | } |
| 225 | |
| 226 | impl<'a> DispQueue<'a> { |
| 227 | fn new(texture_creator: &'a TextureCreator<WindowContext>, width: usize, height: usize, len: usize) -> Self { |
| 228 | let mut pool = Vec::with_capacity(len); |
| 229 | for _ in 0..len + 1 { |
| 230 | let rgb_tex = texture_creator.create_texture_streaming(PixelFormatEnum::RGB24, width as u32, height as u32).expect("failed to create RGB texture"); |
| 231 | let yuv_tex = texture_creator.create_texture_streaming(PixelFormatEnum::IYUV, ((width + 1) & !1) as u32, ((height + 1) & !1) as u32).expect("failed to create YUV texture"); |
| 232 | pool.push(DispFrame{ ts: 0, is_yuv: false, valid: false, rgb_tex, yuv_tex }); |
| 233 | } |
| 234 | pool[len].is_yuv = false; |
| 235 | pool[len].rgb_tex.with_lock(None, |buffer: &mut [u8], _pitch: usize| { |
| 236 | for el in buffer.iter_mut() { *el = 0; } |
| 237 | }).expect("RGB texture could not be locked"); |
| 238 | |
| 239 | Self { pool, first_ts: 0, last_ts: 0, start: 0, end: 0, len, width, height } |
| 240 | } |
| 241 | |
| 242 | fn flush(&mut self) { |
| 243 | self.start = 0; |
| 244 | self.end = 0; |
| 245 | self.first_ts = 0; |
| 246 | self.last_ts = 0; |
| 247 | for frm in self.pool.iter_mut() { |
| 248 | frm.valid = false; |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | fn get_last_texture(&mut self, osd: &OSD) -> &Texture<'a> { |
| 253 | if self.pool[self.len].is_yuv { |
| 254 | if osd.is_active() { |
| 255 | self.pool[self.len].yuv_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| { |
| 256 | osd.draw_yuv(buffer, pitch); |
| 257 | }).expect("YUV texture locking failure"); |
| 258 | } |
| 259 | &self.pool[self.len].yuv_tex |
| 260 | } else { |
| 261 | if osd.is_active() { |
| 262 | self.pool[self.len].rgb_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| { |
| 263 | osd.draw_rgb(buffer, pitch); |
| 264 | }).expect("RGB texture locking failure"); |
| 265 | } |
| 266 | &self.pool[self.len].rgb_tex |
| 267 | } |
| 268 | } |
| 269 | pub fn is_empty(&self) -> bool { self.start == self.end } |
| 270 | pub fn is_full(&self) -> bool { self.len == 0 || self.start == (self.end + 1) % self.len } |
| 271 | pub fn move_end(&mut self) { |
| 272 | self.end += 1; |
| 273 | if self.end >= self.len { |
| 274 | self.end -= self.len; |
| 275 | } |
| 276 | } |
| 277 | pub fn move_start(&mut self) { |
| 278 | self.pool.swap(self.start, self.len); |
| 279 | self.start += 1; |
| 280 | if self.start >= self.len { |
| 281 | self.start -= self.len; |
| 282 | } |
| 283 | if !self.is_empty() { |
| 284 | self.first_ts = self.pool[self.start].ts; |
| 285 | } |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | fn try_display(disp_queue: &mut DispQueue, canvas: &mut Canvas<Window>, osd: &mut OSD, ctime: &TimeKeep) -> Option<u64> { |
| 290 | while !disp_queue.is_empty() { |
| 291 | let disp_time = disp_queue.first_ts; |
| 292 | let ctime = ctime.get_cur_time(); |
| 293 | if disp_time > ctime + 10 { |
| 294 | return Some(disp_time - ctime); |
| 295 | } else if disp_time + 10 < ctime { |
| 296 | disp_queue.move_start(); |
| 297 | } else { |
| 298 | if osd.is_active() { |
| 299 | osd.prepare(ctime); |
| 300 | } |
| 301 | let frm = &mut disp_queue.pool[disp_queue.start]; |
| 302 | let texture = if frm.is_yuv { |
| 303 | if osd.is_active() { |
| 304 | frm.yuv_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| { |
| 305 | osd.draw_yuv(buffer, pitch); |
| 306 | }).expect("YUV texture locking failure"); |
| 307 | } |
| 308 | &frm.yuv_tex |
| 309 | } else { |
| 310 | if osd.is_active() { |
| 311 | frm.rgb_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| { |
| 312 | osd.draw_rgb(buffer, pitch); |
| 313 | }).expect("RGB texture locking failure"); |
| 314 | } |
| 315 | &frm.rgb_tex |
| 316 | }; |
| 317 | canvas.clear(); |
| 318 | canvas.copy(texture, None, None).expect("canvas blit failure"); |
| 319 | canvas.present(); |
| 320 | |
| 321 | disp_queue.move_start(); |
| 322 | if !disp_queue.is_empty() { |
| 323 | return Some((disp_queue.first_ts - ctime).saturating_sub(2)); |
| 324 | } else { |
| 325 | return None; |
| 326 | } |
| 327 | } |
| 328 | } |
| 329 | None |
| 330 | } |
| 331 | |
| 332 | struct Player { |
| 333 | sdl_context: sdl2::Sdl, |
| 334 | vsystem: sdl2::VideoSubsystem, |
| 335 | asystem: sdl2::AudioSubsystem, |
| 336 | |
| 337 | acontrol: AudioControl, |
| 338 | vcontrol: VideoControl, |
| 339 | |
| 340 | play_video: bool, |
| 341 | play_audio: bool, |
| 342 | has_video: bool, |
| 343 | has_audio: bool, |
| 344 | video_str: u32, |
| 345 | audio_str: u32, |
| 346 | sc_size: ScaleSize, |
| 347 | |
| 348 | vthreads: usize, |
| 349 | use_mt: bool, |
| 350 | #[cfg(feature="hwaccel")] |
| 351 | use_hwaccel: bool, |
| 352 | |
| 353 | paused: bool, |
| 354 | mute: bool, |
| 355 | volume: usize, |
| 356 | end: bool, |
| 357 | |
| 358 | tkeep: TimeKeep, |
| 359 | |
| 360 | debug: bool, |
| 361 | osd: OSD, |
| 362 | |
| 363 | #[cfg(feature="debug")] |
| 364 | logfile: File, |
| 365 | } |
| 366 | |
| 367 | impl Player { |
| 368 | fn new() -> Self { |
| 369 | let sdl_context = sdl2::init().expect("SDL2 init failure"); |
| 370 | let vsystem = sdl_context.video().expect("video subsystem init failure"); |
| 371 | let asystem = sdl_context.audio().expect("audio subsystem init failure"); |
| 372 | vsystem.disable_screen_saver(); |
| 373 | let acontrol = AudioControl::new(None, None, false, &asystem); |
| 374 | let vcontrol = VideoControl::new(None, 0, 0, 0, 0); |
| 375 | Self { |
| 376 | sdl_context, asystem, vsystem, |
| 377 | |
| 378 | acontrol, vcontrol, |
| 379 | |
| 380 | play_video: true, |
| 381 | play_audio: true, |
| 382 | has_video: false, |
| 383 | has_audio: false, |
| 384 | video_str: 0, |
| 385 | audio_str: 0, |
| 386 | sc_size: ScaleSize::Auto, |
| 387 | |
| 388 | vthreads: 3, |
| 389 | use_mt: true, |
| 390 | #[cfg(feature="hwaccel")] |
| 391 | use_hwaccel: true, |
| 392 | |
| 393 | paused: false, |
| 394 | mute: false, |
| 395 | volume: 100, |
| 396 | end: false, |
| 397 | |
| 398 | tkeep: TimeKeep::new(), |
| 399 | |
| 400 | debug: false, |
| 401 | osd: OSD::new(), |
| 402 | |
| 403 | #[cfg(feature="debug")] |
| 404 | logfile: File::create("debug.log").expect("'debug.log' should be available for writing"), |
| 405 | } |
| 406 | } |
| 407 | fn seek(&mut self, off: u64, fwd: bool, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> Result<(), ()> { |
| 408 | let cur_time = self.tkeep.get_cur_time(); |
| 409 | let seektime = if fwd { cur_time + off * 1000 } else { |
| 410 | cur_time.saturating_sub(off * 1000) }; |
| 411 | debug_log!(self; {format!(" seek to {}", seektime)}); |
| 412 | |
| 413 | let ret = dmx.seek(NATimePoint::Milliseconds(seektime)); |
| 414 | if ret.is_err() { |
| 415 | println!(" seek error"); |
| 416 | return Ok(()); //TODO: not ignore some of seek errors? |
| 417 | } |
| 418 | |
| 419 | self.acontrol.flush(); |
| 420 | self.vcontrol.flush(); |
| 421 | disp_queue.flush(); |
| 422 | |
| 423 | self.tkeep.reset_ts(); |
| 424 | self.prefill(dmx, disp_queue)?; |
| 425 | if !disp_queue.is_empty() { |
| 426 | self.tkeep.reset_all(disp_queue.first_ts); |
| 427 | } else { |
| 428 | let mut iterations = 0; |
| 429 | let mut time = self.acontrol.get_time(); |
| 430 | while time.is_none() { |
| 431 | iterations += 1; |
| 432 | std::thread::yield_now(); |
| 433 | if iterations > 1000000 { println!(" still no time set?!"); break; } |
| 434 | time = self.acontrol.get_time(); |
| 435 | } |
| 436 | if let Some(time) = time { |
| 437 | self.tkeep.reset_all(time); |
| 438 | } |
| 439 | } |
| 440 | if !self.paused { |
| 441 | self.acontrol.resume(); |
| 442 | } |
| 443 | Ok(()) |
| 444 | } |
| 445 | fn prefill(&mut self, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> Result<(), ()> { |
| 446 | debug_log!(self; {" prefilling"}); |
| 447 | while self.vcontrol.get_queue_size() < FRAME_QUEUE_LEN { |
| 448 | let mut try_send = self.acontrol.get_queue_size() < FRAME_QUEUE_LEN && (!self.has_video || (!self.vcontrol.is_filled(FRAME_QUEUE_LEN) && !disp_queue.is_full())); |
| 449 | |
| 450 | if !self.vcontrol.try_send_queued() && self.vcontrol.get_queue_size() > FRAME_QUEUE_LEN / 2 { |
| 451 | try_send = false; |
| 452 | } |
| 453 | if !self.acontrol.try_send_queued() && self.acontrol.get_queue_size() > FRAME_QUEUE_LEN / 2 { |
| 454 | try_send = false; |
| 455 | } |
| 456 | if try_send { |
| 457 | match dmx.get_frame() { |
| 458 | Err(DemuxerError::EOF) => break, |
| 459 | Err(_) => break, |
| 460 | Ok(pkt) => { |
| 461 | let streamno = pkt.get_stream().get_id(); |
| 462 | if self.has_video && streamno == self.video_str { |
| 463 | self.vcontrol.try_send_video(PktSendEvent::Packet(pkt)); |
| 464 | } else if self.has_audio && streamno == self.audio_str { |
| 465 | self.acontrol.try_send_audio(PktSendEvent::Packet(pkt)); |
| 466 | } |
| 467 | } |
| 468 | }; |
| 469 | } |
| 470 | self.vcontrol.fill(disp_queue); |
| 471 | |
| 472 | if !try_send { |
| 473 | break; |
| 474 | } |
| 475 | } |
| 476 | if self.has_video { |
| 477 | while self.vcontrol.get_queue_size() > 0 && !disp_queue.is_full() { |
| 478 | self.vcontrol.try_send_queued(); |
| 479 | self.vcontrol.fill(disp_queue); |
| 480 | std::thread::sleep(Duration::from_millis(10)); |
| 481 | } |
| 482 | self.vcontrol.wait_for_frames()?; |
| 483 | self.vcontrol.fill(disp_queue); |
| 484 | } |
| 485 | debug_log!(self; {format!(" prefilling done, frames {}-{} audio {}", disp_queue.start, disp_queue.end, self.acontrol.get_fill())}); |
| 486 | Ok(()) |
| 487 | } |
| 488 | fn toggle_pause(&mut self) { |
| 489 | self.paused = !self.paused; |
| 490 | if self.paused { |
| 491 | self.vsystem.enable_screen_saver(); |
| 492 | self.tkeep.set_ts(); |
| 493 | } else { |
| 494 | self.vsystem.disable_screen_saver(); |
| 495 | self.tkeep.set_time(); |
| 496 | } |
| 497 | if self.paused { |
| 498 | self.acontrol.pause(); |
| 499 | } else { |
| 500 | self.acontrol.resume(); |
| 501 | } |
| 502 | } |
| 503 | fn handle_events(&mut self, event_pump: &mut sdl2::EventPump, canvas: &mut Canvas<Window>, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> Result<bool, ()> { |
| 504 | for event in event_pump.poll_iter() { |
| 505 | if let Event::Quit {..} = event { |
| 506 | self.end = true; |
| 507 | println!(); |
| 508 | return Ok(true); |
| 509 | } |
| 510 | if let Event::Window {win_event: WindowEvent::Exposed, ..} = event { |
| 511 | canvas.clear(); |
| 512 | canvas.copy(disp_queue.get_last_texture(&self.osd), None, None).expect("blitting failure"); |
| 513 | canvas.present(); |
| 514 | } |
| 515 | if let Event::MouseButtonDown {mouse_btn, ..} = event { |
| 516 | match mouse_btn { |
| 517 | MouseButton::Right => self.toggle_pause(), |
| 518 | MouseButton::Middle => self.osd.toggle(), |
| 519 | _ => {}, |
| 520 | }; |
| 521 | } |
| 522 | if let Event::MouseWheel {direction: MouseWheelDirection::Normal, x: 0, y, ..} = event { |
| 523 | self.seek(10, y > 0, dmx, disp_queue)?; |
| 524 | } |
| 525 | if let Event::KeyDown {keycode: Some(keycode), keymod, ..} = event { |
| 526 | match keycode { |
| 527 | Keycode::Escape => { |
| 528 | self.end = true; |
| 529 | println!(); |
| 530 | return Ok(true); |
| 531 | }, |
| 532 | Keycode::Q if keymod.contains(Mod::RSHIFTMOD) || keymod.contains(Mod::LSHIFTMOD) => { |
| 533 | self.end = true; |
| 534 | println!(); |
| 535 | return Ok(true); |
| 536 | }, |
| 537 | Keycode::Return | Keycode::KpEnter => return Ok(true), |
| 538 | Keycode::R => { self.seek(0, true, dmx, disp_queue)?; }, |
| 539 | Keycode::Right | Keycode::Kp6 => { self.seek(10, true, dmx, disp_queue)?; }, |
| 540 | Keycode::Left | Keycode::Kp4 => { self.seek(10, false, dmx, disp_queue)?; }, |
| 541 | Keycode::Up | Keycode::Kp8 => { self.seek(60, true, dmx, disp_queue)?; }, |
| 542 | Keycode::Down | Keycode::Kp2 => { self.seek(60, false, dmx, disp_queue)?; }, |
| 543 | Keycode::PageUp | Keycode::Kp9 => { self.seek(600, true, dmx, disp_queue)?; }, |
| 544 | Keycode::PageDown | Keycode::Kp3 => { self.seek(600, false, dmx, disp_queue)?; }, |
| 545 | Keycode::Space => { self.toggle_pause(); }, |
| 546 | Keycode::Plus | Keycode::KpPlus => { |
| 547 | self.volume = (self.volume + 10).min(MAX_VOLUME); |
| 548 | if !self.mute { |
| 549 | self.acontrol.set_volume(self.volume); |
| 550 | } |
| 551 | }, |
| 552 | Keycode::Minus | Keycode::KpMinus => { |
| 553 | self.volume = self.volume.saturating_sub(10); |
| 554 | if !self.mute { |
| 555 | self.acontrol.set_volume(self.volume); |
| 556 | } |
| 557 | }, |
| 558 | Keycode::D => { |
| 559 | self.debug = !self.debug; |
| 560 | }, |
| 561 | Keycode::M => { |
| 562 | self.mute = !self.mute; |
| 563 | if self.mute { |
| 564 | self.acontrol.set_volume(0); |
| 565 | } else { |
| 566 | self.acontrol.set_volume(self.volume); |
| 567 | } |
| 568 | }, |
| 569 | Keycode::H => { |
| 570 | self.vcontrol.try_send_video(PktSendEvent::HurryUp); |
| 571 | }, |
| 572 | Keycode::O => { |
| 573 | if keymod.contains(Mod::RSHIFTMOD) || keymod.contains(Mod::LSHIFTMOD) { |
| 574 | self.osd.toggle_perm(); |
| 575 | } else { |
| 576 | self.osd.toggle(); |
| 577 | } |
| 578 | }, |
| 579 | _ => {}, |
| 580 | }; |
| 581 | if !self.paused { |
| 582 | print!("{:60}\r", ' '); |
| 583 | std::io::stdout().flush().unwrap(); |
| 584 | } |
| 585 | } |
| 586 | } |
| 587 | Ok(false) |
| 588 | } |
| 589 | fn play(&mut self, mut window: Window, name: &str, start_time: NATimePoint) -> Window { |
| 590 | debug_log!(self; {format!("Playing {}", name)}); |
| 591 | |
| 592 | // prepare data source |
| 593 | let path = Path::new(name); |
| 594 | let mut file = if let Ok(handle) = File::open(path) { |
| 595 | if let Ok(meta) = handle.metadata() { |
| 596 | if meta.is_dir() { |
| 597 | return window; |
| 598 | } |
| 599 | } |
| 600 | handle |
| 601 | } else { |
| 602 | println!("failed to open {}", name); |
| 603 | return window; |
| 604 | }; |
| 605 | let mut fr = FileReader::new_read(&mut file); |
| 606 | let mut br = ByteReader::new(&mut fr); |
| 607 | let res = detect::detect_format(name, &mut br); |
| 608 | if res.is_none() { |
| 609 | println!("cannot detect format for {}", name); |
| 610 | return window; |
| 611 | } |
| 612 | let (dmx_name, _score) = res.unwrap(); |
| 613 | debug_log!(self; {format!(" found demuxer {} with score {:?}", dmx_name, _score)}); |
| 614 | println!("trying demuxer {} on {}", dmx_name, name); |
| 615 | |
| 616 | let mut dmx_reg = RegisteredDemuxers::new(); |
| 617 | nihav_register_all_demuxers(&mut dmx_reg); |
| 618 | let mut dec_reg = RegisteredDecoders::new(); |
| 619 | nihav_register_all_decoders(&mut dec_reg); |
| 620 | let mut mtdec_reg = RegisteredMTDecoders::new(); |
| 621 | if self.use_mt { |
| 622 | nihav_register_all_mt_decoders(&mut mtdec_reg); |
| 623 | } |
| 624 | |
| 625 | let ret = dmx_reg.find_demuxer(dmx_name); |
| 626 | if ret.is_none() { |
| 627 | println!("error finding {} demuxer", dmx_name); |
| 628 | return window; |
| 629 | } |
| 630 | let dmx_fact = ret.unwrap(); |
| 631 | br.seek(SeekFrom::Start(0)).expect("should be able to seek to the start"); |
| 632 | let ret = create_demuxer(dmx_fact, &mut br); |
| 633 | if ret.is_err() { |
| 634 | println!("error creating demuxer"); |
| 635 | return window; |
| 636 | } |
| 637 | let mut dmx = ret.unwrap(); |
| 638 | if start_time != NATimePoint::None { |
| 639 | debug_log!(self; {format!(" start seek to {}", start_time)}); |
| 640 | if dmx.seek(start_time).is_err() { |
| 641 | println!("initial seek failed"); |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | let mut width = 640; |
| 646 | let mut height = 480; |
| 647 | let mut tb_num = 0; |
| 648 | let mut tb_den = 0; |
| 649 | let mut ainfo: Option<NAAudioInfo> = None; |
| 650 | let mut sbr_hack = false; |
| 651 | |
| 652 | let mut video_dec: Option<DecoderStuff> = None; |
| 653 | let mut audio_dec: Option<DecoderStuff> = None; |
| 654 | |
| 655 | let duration = dmx.get_duration(); |
| 656 | if duration != 0 { |
| 657 | println!(" total duration {}", format_time(duration)); |
| 658 | } |
| 659 | self.has_video = false; |
| 660 | self.has_audio = false; |
| 661 | self.osd.reset(); |
| 662 | self.osd.set_duration(duration); |
| 663 | for i in 0..dmx.get_num_streams() { |
| 664 | let s = dmx.get_stream(i).unwrap(); |
| 665 | let info = s.get_info(); |
| 666 | let decfunc = dec_reg.find_decoder(info.get_name()); |
| 667 | let decfunc_mt = mtdec_reg.find_decoder(info.get_name()); |
| 668 | println!("stream {} - {} {}", i, s, info.get_name()); |
| 669 | debug_log!(self; {format!(" stream {} - {} {}", i, s, info.get_name())}); |
| 670 | let str_id = s.get_id(); |
| 671 | if info.is_video() { |
| 672 | if video_dec.is_none() && self.play_video { |
| 673 | #[cfg(feature="hwaccel")] |
| 674 | if info.get_name() == "h264" && self.use_hwaccel { |
| 675 | let mut dec = new_h264_hwdec(); |
| 676 | let dsupp = Box::new(NADecoderSupport::new()); |
| 677 | let props = info.get_properties().get_video_info().unwrap(); |
| 678 | if props.get_width() != 0 { |
| 679 | width = props.get_width(); |
| 680 | height = props.get_height(); |
| 681 | } |
| 682 | if dec.init(info.clone()).is_err() { |
| 683 | println!("failed to initialise hwaccel video decoder"); |
| 684 | } else { |
| 685 | video_dec = Some(DecoderStuff{ dsupp, dec: DecoderType::VideoHW(dec) }); |
| 686 | self.video_str = str_id; |
| 687 | let (tbn, tbd) = s.get_timebase(); |
| 688 | tb_num = tbn; |
| 689 | tb_den = tbd; |
| 690 | self.has_video = true; |
| 691 | println!(" using hardware-accelerated decoding"); |
| 692 | continue; |
| 693 | } |
| 694 | } |
| 695 | if let Some(decfunc) = decfunc_mt { |
| 696 | let mut dec = (decfunc)(); |
| 697 | let mut dsupp = Box::new(NADecoderSupport::new()); |
| 698 | let props = info.get_properties().get_video_info().unwrap(); |
| 699 | if props.get_width() != 0 { |
| 700 | width = props.get_width(); |
| 701 | height = props.get_height(); |
| 702 | } |
| 703 | if dec.init(&mut dsupp, info.clone(), self.vthreads).is_ok() { |
| 704 | video_dec = Some(DecoderStuff{ dsupp, dec: DecoderType::VideoMT(dec, MTFrameReorderer::new()) }); |
| 705 | self.video_str = str_id; |
| 706 | let (tbn, tbd) = s.get_timebase(); |
| 707 | tb_num = tbn; |
| 708 | tb_den = tbd; |
| 709 | self.has_video = true; |
| 710 | continue; |
| 711 | } else { |
| 712 | println!("failed to create multi-threaded decoder, falling back"); |
| 713 | } |
| 714 | } |
| 715 | if let Some(decfunc) = decfunc { |
| 716 | let mut dec = (decfunc)(); |
| 717 | let mut dsupp = Box::new(NADecoderSupport::new()); |
| 718 | let props = info.get_properties().get_video_info().unwrap(); |
| 719 | if props.get_width() != 0 { |
| 720 | width = props.get_width(); |
| 721 | height = props.get_height(); |
| 722 | } |
| 723 | let desc = get_codec_description(info.get_name()); |
| 724 | let (reorder_depth, reord) = if desc.is_none() || (desc.unwrap().caps & CODEC_CAP_COMPLEX_REORDER) == 0 { |
| 725 | let reord: Box<dyn FrameReorderer + Send> = Box::new(IPBReorderer::new()); |
| 726 | (3, reord) |
| 727 | } else { |
| 728 | let reord: Box<dyn FrameReorderer + Send> = Box::new(ComplexReorderer::new()); |
| 729 | (16, reord) |
| 730 | }; |
| 731 | dsupp.pool_u8 = NAVideoBufferPool::new(reorder_depth); |
| 732 | dsupp.pool_u16 = NAVideoBufferPool::new(reorder_depth); |
| 733 | dsupp.pool_u32 = NAVideoBufferPool::new(reorder_depth); |
| 734 | if dec.init(&mut dsupp, info).is_err() { |
| 735 | println!("failed to initialise video decoder"); |
| 736 | return window; |
| 737 | } |
| 738 | video_dec = Some(DecoderStuff{ dsupp, dec: DecoderType::Video(dec, reord) }); |
| 739 | self.video_str = str_id; |
| 740 | let (tbn, tbd) = s.get_timebase(); |
| 741 | tb_num = tbn; |
| 742 | tb_den = tbd; |
| 743 | self.has_video = true; |
| 744 | } else { |
| 745 | println!("no video decoder for {} found!", info.get_name()); |
| 746 | } |
| 747 | } |
| 748 | } else if info.is_audio() { |
| 749 | if audio_dec.is_none() && self.play_audio { |
| 750 | if let Some(decfunc) = decfunc { |
| 751 | let mut dec = (decfunc)(); |
| 752 | let mut dsupp = Box::new(NADecoderSupport::new()); |
| 753 | ainfo = info.get_properties().get_audio_info(); |
| 754 | if let (true, Some(ref ai)) = (info.get_name() == "aac", ainfo) { |
| 755 | if ai.sample_rate < 32000 { |
| 756 | sbr_hack = true; |
| 757 | } |
| 758 | } |
| 759 | if dec.init(&mut dsupp, info).is_err() { |
| 760 | println!("failed to initialise audio decoder"); |
| 761 | return window; |
| 762 | } |
| 763 | audio_dec = Some(DecoderStuff{ dsupp, dec: DecoderType::Audio(dec) }); |
| 764 | self.audio_str = str_id; |
| 765 | self.has_audio = true; |
| 766 | } else { |
| 767 | println!("no audio decoder for {} found!", info.get_name()); |
| 768 | } |
| 769 | } |
| 770 | } else { |
| 771 | println!("decoder {} not found", info.get_name()); |
| 772 | } |
| 773 | } |
| 774 | if !self.has_video && !self.has_audio { |
| 775 | println!("No playable streams found."); |
| 776 | return window; |
| 777 | } |
| 778 | |
| 779 | match self.sc_size { |
| 780 | ScaleSize::Auto => { |
| 781 | while (width <= 384) && (height <= 288) { |
| 782 | width <<= 1; |
| 783 | height <<= 1; |
| 784 | } |
| 785 | }, |
| 786 | ScaleSize::Times(factor) => { |
| 787 | let nw = ((width as f32) * factor).ceil() as usize; |
| 788 | let nh = ((height as f32) * factor).ceil() as usize; |
| 789 | if nw > 0 && nh > 0 { |
| 790 | width = nw; |
| 791 | height = nh; |
| 792 | } |
| 793 | }, |
| 794 | ScaleSize::Fixed(w, h) => { |
| 795 | width = w; |
| 796 | height = h; |
| 797 | }, |
| 798 | }; |
| 799 | |
| 800 | // prepare playback structure |
| 801 | let mut new_vcontrol = VideoControl::new(video_dec, width, height, tb_num, tb_den); |
| 802 | std::mem::swap(&mut self.vcontrol, &mut new_vcontrol); |
| 803 | |
| 804 | let mut new_acontrol = AudioControl::new(audio_dec, ainfo, sbr_hack, &self.asystem); |
| 805 | std::mem::swap(&mut self.acontrol, &mut new_acontrol); |
| 806 | |
| 807 | if self.mute { |
| 808 | self.acontrol.set_volume(0); |
| 809 | } else { |
| 810 | self.acontrol.set_volume(self.volume); |
| 811 | } |
| 812 | |
| 813 | let fname = path.file_name(); |
| 814 | let wname = if let Some(fname) = fname { |
| 815 | // workaround for libSDL2 workaround for non-UTF8 windowing systems |
| 816 | // see https://github.com/libsdl-org/SDL/pull/4290 for detais |
| 817 | let nname = fname.to_str().expect("should be able to set window title").replace('\u{2013}', "-").replace('\u{2014}', "-"); |
| 818 | "NihAV player - ".to_owned() + &nname |
| 819 | } else { |
| 820 | "NihAV player".to_owned() |
| 821 | }; |
| 822 | window.set_title(&wname).expect("set window title"); |
| 823 | if window.size() != (width as u32, height as u32) { |
| 824 | window.set_size(width as u32, height as u32).expect("resize window"); |
| 825 | } |
| 826 | window.show(); |
| 827 | let mut canvas = window.into_canvas().build().expect("should be able to build canvas"); |
| 828 | let texture_creator = canvas.texture_creator(); |
| 829 | let mut disp_q = DispQueue::new(&texture_creator, width, height, if self.has_video { FRAME_QUEUE_LEN } else { 0 }); |
| 830 | if !self.has_video { |
| 831 | canvas.clear(); |
| 832 | canvas.copy(disp_q.get_last_texture(&self.osd), None, None).expect("blit failure"); |
| 833 | canvas.present(); |
| 834 | } |
| 835 | |
| 836 | self.has_audio = self.acontrol.has_audio(); |
| 837 | if !self.has_video && !self.has_audio { |
| 838 | println!("No playable streams."); |
| 839 | return canvas.into_window(); |
| 840 | } |
| 841 | |
| 842 | // play |
| 843 | if self.prefill(&mut dmx, &mut disp_q).is_err() { |
| 844 | std::mem::swap(&mut self.vcontrol, &mut new_vcontrol); |
| 845 | new_vcontrol.finish(); |
| 846 | std::mem::swap(&mut self.acontrol, &mut new_acontrol); |
| 847 | new_acontrol.finish(); |
| 848 | return canvas.into_window(); |
| 849 | } |
| 850 | self.tkeep.reset_all(if !disp_q.is_empty() { disp_q.first_ts } else { 0 }); |
| 851 | if !self.paused { |
| 852 | self.acontrol.resume(); |
| 853 | } |
| 854 | let mut event_pump = self.sdl_context.event_pump().expect("should be able to create event pump"); |
| 855 | let mut last_disp = Instant::now(); |
| 856 | let mut has_data = true; |
| 857 | 'main: loop { |
| 858 | let ret = self.handle_events(&mut event_pump, &mut canvas, &mut dmx, &mut disp_q); |
| 859 | if matches!(ret, Ok(true) | Err(_)) { |
| 860 | println!(); |
| 861 | break 'main; |
| 862 | } |
| 863 | if !self.paused { |
| 864 | let mut try_send = self.acontrol.get_queue_size() < FRAME_QUEUE_LEN && self.vcontrol.get_queue_size() < FRAME_QUEUE_LEN; |
| 865 | if !self.vcontrol.try_send_queued() && self.vcontrol.is_filled(FRAME_QUEUE_LEN) { |
| 866 | try_send = false; |
| 867 | } |
| 868 | if !self.acontrol.try_send_queued() { |
| 869 | try_send = false; |
| 870 | } |
| 871 | while has_data && try_send { |
| 872 | match dmx.get_frame() { |
| 873 | Err(DemuxerError::EOF) => { |
| 874 | self.vcontrol.try_send_video(PktSendEvent::End); |
| 875 | self.acontrol.try_send_audio(PktSendEvent::End); |
| 876 | has_data = false; |
| 877 | }, |
| 878 | Err(err) => { |
| 879 | println!("demuxer error {:?}", err); |
| 880 | if err == DemuxerError::IOError { |
| 881 | self.vcontrol.try_send_video(PktSendEvent::End); |
| 882 | self.acontrol.try_send_audio(PktSendEvent::End); |
| 883 | has_data = false; |
| 884 | } |
| 885 | }, |
| 886 | Ok(pkt) => { |
| 887 | let streamno = pkt.get_stream().get_id(); |
| 888 | if self.has_video && streamno == self.video_str { |
| 889 | debug_log!(self; {" sending video packet"}); |
| 890 | self.vcontrol.try_send_video(PktSendEvent::Packet(pkt)); |
| 891 | if self.vcontrol.is_filled(FRAME_QUEUE_LEN) { |
| 892 | try_send = false; |
| 893 | } |
| 894 | } else if self.has_audio && streamno == self.audio_str { |
| 895 | debug_log!(self; {" sending audio packet"}); |
| 896 | self.acontrol.try_send_audio(PktSendEvent::Packet(pkt)); |
| 897 | if self.acontrol.get_queue_size() >= FRAME_QUEUE_LEN { |
| 898 | try_send = false; |
| 899 | } |
| 900 | } |
| 901 | } |
| 902 | }; |
| 903 | } |
| 904 | self.vcontrol.fill(&mut disp_q); |
| 905 | let mut sleep_time = 25; |
| 906 | debug_log!(self; {format!(" time {}", self.tkeep.get_cur_time())}); |
| 907 | if self.has_video { |
| 908 | debug_log!(self; {format!(" disp queue {}-{}, {}-{} vqueue fill {}", disp_q.first_ts, disp_q.last_ts, disp_q.start, disp_q.end, self.vcontrol.get_queue_size())}); |
| 909 | let ret = try_display(&mut disp_q, &mut canvas, &mut self.osd, &self.tkeep); |
| 910 | if let Some(next_time) = ret { |
| 911 | sleep_time = sleep_time.min(next_time); |
| 912 | } |
| 913 | } |
| 914 | if self.has_audio { |
| 915 | let time_left = self.acontrol.get_time_left(); |
| 916 | debug_log!(self; {format!(" audio left {}", time_left)}); |
| 917 | sleep_time = sleep_time.min(time_left); |
| 918 | } |
| 919 | debug_log!(self; {format!(" sleep {}ms", sleep_time)}); |
| 920 | if last_disp.elapsed().as_millis() >= 10 { |
| 921 | let c_time = self.tkeep.get_cur_time(); |
| 922 | |
| 923 | if !self.debug { |
| 924 | print!(" {} {}% \r", format_time(c_time), self.acontrol.get_volume()); |
| 925 | } else { |
| 926 | print!(" {} {} {}% {:3} {:6}\r", format_time(c_time), if self.vcontrol.is_yuv() { 'Y' } else { 'R' }, self.acontrol.get_volume(), (disp_q.end + disp_q.len - disp_q.start) % disp_q.len, self.acontrol.get_fill()); |
| 927 | } |
| 928 | std::io::stdout().flush().unwrap(); |
| 929 | last_disp = Instant::now(); |
| 930 | } |
| 931 | let mut end = true; |
| 932 | if self.has_video && !self.vcontrol.is_video_end() { |
| 933 | end = false; |
| 934 | } |
| 935 | if self.has_audio && !self.acontrol.is_audio_end() { |
| 936 | end = false; |
| 937 | } |
| 938 | if end { |
| 939 | break; |
| 940 | } |
| 941 | thread::sleep(Duration::from_millis(sleep_time)); |
| 942 | } else { |
| 943 | thread::sleep(Duration::from_millis(20)); |
| 944 | } |
| 945 | } |
| 946 | println!(); |
| 947 | std::mem::swap(&mut self.vcontrol, &mut new_vcontrol); |
| 948 | new_vcontrol.finish(); |
| 949 | std::mem::swap(&mut self.acontrol, &mut new_acontrol); |
| 950 | new_acontrol.finish(); |
| 951 | canvas.into_window() |
| 952 | } |
| 953 | } |
| 954 | |
| 955 | fn main() { |
| 956 | let args: Vec<String> = env::args().collect(); |
| 957 | |
| 958 | if args.len() == 1 { |
| 959 | println!("usage: nihav-player file1 file2 ..."); |
| 960 | return; |
| 961 | } |
| 962 | |
| 963 | let mut player = Player::new(); |
| 964 | let mut builder = player.vsystem.window("NihAV Player", 640, 480); |
| 965 | let mut window = builder.position_centered().hidden().build().expect("should be able to centre window"); |
| 966 | |
| 967 | let mut aiter = args.iter().skip(1); |
| 968 | let mut seek_time = NATimePoint::None; |
| 969 | while let Some(arg) = aiter.next() { |
| 970 | match arg.as_str() { |
| 971 | "-an" => { player.play_audio = false; }, |
| 972 | "-ae" => { player.play_audio = true; }, |
| 973 | "-vn" => { player.play_video = false; }, |
| 974 | "-ve" => { player.play_video = true; }, |
| 975 | "-seek" | "-start" => { |
| 976 | if let Some(arg) = aiter.next() { |
| 977 | if let Ok(time) = arg.parse::<NATimePoint>() { |
| 978 | seek_time = time; |
| 979 | } else { |
| 980 | println!("wrong seek time"); |
| 981 | seek_time = NATimePoint::None; |
| 982 | } |
| 983 | } |
| 984 | }, |
| 985 | "-vol" => { |
| 986 | if let Some(arg) = aiter.next() { |
| 987 | if let Ok(vol) = arg.parse::<usize>() { |
| 988 | player.volume = vol.min(MAX_VOLUME); |
| 989 | } else { |
| 990 | println!("wrong volume"); |
| 991 | } |
| 992 | } |
| 993 | }, |
| 994 | "-debug" => { |
| 995 | player.debug = true; |
| 996 | }, |
| 997 | "-nodebug" => { |
| 998 | player.debug = false; |
| 999 | }, |
| 1000 | "-mt" => { |
| 1001 | player.use_mt = true; |
| 1002 | }, |
| 1003 | "-nomt" => { |
| 1004 | player.use_mt = false; |
| 1005 | }, |
| 1006 | #[cfg(feature="hwaccel")] |
| 1007 | "-hwaccel" => { |
| 1008 | player.use_hwaccel = true; |
| 1009 | }, |
| 1010 | #[cfg(feature="hwaccel")] |
| 1011 | "-nohwaccel" => { |
| 1012 | player.use_hwaccel = false; |
| 1013 | }, |
| 1014 | "-threads" => { |
| 1015 | if let Some(arg) = aiter.next() { |
| 1016 | if let Ok(val) = arg.parse::<usize>() { |
| 1017 | player.vthreads = val.max(1); |
| 1018 | } else { |
| 1019 | println!("wrong number of threads"); |
| 1020 | } |
| 1021 | } |
| 1022 | }, |
| 1023 | "-scale" => { |
| 1024 | if let Some(arg) = aiter.next() { |
| 1025 | if let Ok(ssize) = arg.parse::<ScaleSize>() { |
| 1026 | player.sc_size = ssize; |
| 1027 | } else { |
| 1028 | println!("invalid scale size"); |
| 1029 | } |
| 1030 | } |
| 1031 | }, |
| 1032 | _ => { |
| 1033 | window = player.play(window, arg, seek_time); |
| 1034 | if player.end { break; } |
| 1035 | seek_time = NATimePoint::None; |
| 1036 | }, |
| 1037 | }; |
| 1038 | } |
| 1039 | } |