]> git.nihav.org Git - nihav-player.git/blobdiff - videoplayer/src/main.rs
try to improve state handling in decoding threads
[nihav-player.git] / videoplayer / src / main.rs
index dcd4d0a3538fa51e2a15372f506c11cfbf032137..fa2c4d5bb143bdd9d1b788c33a3f0e0ade6dcdcf 100644 (file)
@@ -9,9 +9,11 @@ use std::io::Write;
 use std::path::Path;
 use std::time::{Duration, Instant};
 use std::thread;
+use std::sync::atomic::{AtomicU8, Ordering};
 
 use sdl2::event::{Event, WindowEvent};
 use sdl2::keyboard::Keycode;
+use sdl2::mouse::MouseButton;
 use sdl2::render::{Canvas, Texture, TextureCreator};
 use sdl2::pixels::PixelFormatEnum;
 use sdl2::video::{Window, WindowContext};
@@ -29,6 +31,57 @@ mod audiodec;
 use audiodec::*;
 mod videodec;
 use videodec::*;
+mod osd;
+use osd::*;
+
+#[repr(u8)]
+#[derive(Clone,Copy,Debug,PartialEq)]
+enum DecodingState {
+    Normal,
+    Waiting,
+    Flush,
+    Prefetch,
+    Error,
+    End,
+}
+
+impl Default for DecodingState {
+    fn default() -> Self { DecodingState::Normal }
+}
+
+impl From<u8> for DecodingState {
+    fn from(val: u8) -> Self {
+        match val {
+            0 => DecodingState::Normal,
+            1 => DecodingState::Waiting,
+            2 => DecodingState::Flush,
+            3 => DecodingState::Prefetch,
+            4 => DecodingState::End,
+            _ => DecodingState::Error,
+        }
+    }
+}
+
+struct DecoderState {
+    state:  AtomicU8
+}
+
+impl DecoderState {
+    const fn new() -> Self {
+        Self {
+            state:  AtomicU8::new(DecodingState::Normal as u8)
+        }
+    }
+    fn set_state(&self, state: DecodingState) {
+        self.state.store(state as u8, Ordering::Release);
+    }
+    fn get_state(&self) -> DecodingState {
+        self.state.load(Ordering::Acquire).into()
+    }
+    fn is_flushing(&self) -> bool {
+        matches!(self.get_state(), DecodingState::Flush | DecodingState::Error)
+    }
+}
 
 #[cfg(feature="debug")]
 macro_rules! debug_log {
@@ -159,10 +212,20 @@ impl<'a> DispQueue<'a> {
         }
     }
 
-    fn get_last_texture(&self) -> &Texture<'a> {
+    fn get_last_texture(&mut self, osd: &OSD) -> &Texture<'a> {
         if self.pool[self.len].is_yuv {
+            if osd.is_active() {
+                self.pool[self.len].yuv_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
+                        osd.draw_yuv(buffer, pitch);
+                    }).unwrap();
+            }
             &self.pool[self.len].yuv_tex
         } else {
+            if osd.is_active() {
+                self.pool[self.len].rgb_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
+                        osd.draw_rgb(buffer, pitch);
+                    }).unwrap();
+            }
             &self.pool[self.len].rgb_tex
         }
     }
@@ -186,7 +249,7 @@ impl<'a> DispQueue<'a> {
     }
 }
 
-fn try_display(disp_queue: &mut DispQueue, canvas: &mut Canvas<Window>, ctime: &TimeKeep) -> Option<u64> {
+fn try_display(disp_queue: &mut DispQueue, canvas: &mut Canvas<Window>, osd: &mut OSD, ctime: &TimeKeep) -> Option<u64> {
     while !disp_queue.is_empty() {
         let disp_time = disp_queue.first_ts;
         let ctime = ctime.get_cur_time();
@@ -195,8 +258,25 @@ fn try_display(disp_queue: &mut DispQueue, canvas: &mut Canvas<Window>, ctime: &
         } else if disp_time + 10 < ctime {
             disp_queue.move_start();
         } else {
-            let frm = &disp_queue.pool[disp_queue.start];
-            let texture = if frm.is_yuv { &frm.yuv_tex } else { &frm.rgb_tex };
+            if osd.is_active() {
+                osd.prepare(ctime);
+            }
+            let frm = &mut disp_queue.pool[disp_queue.start];
+            let texture = if frm.is_yuv {
+                    if osd.is_active() {
+                        frm.yuv_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
+                                osd.draw_yuv(buffer, pitch);
+                            }).unwrap();
+                    }
+                    &frm.yuv_tex
+                } else {
+                    if osd.is_active() {
+                        frm.rgb_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
+                                osd.draw_rgb(buffer, pitch);
+                            }).unwrap();
+                    }
+                    &frm.rgb_tex
+                };
             canvas.clear();
             canvas.copy(texture, None, None).unwrap();
             canvas.present();
@@ -216,6 +296,8 @@ struct Player {
     sdl_context:    sdl2::Sdl,
     vsystem:        sdl2::VideoSubsystem,
     asystem:        sdl2::AudioSubsystem,
+    xpos:           Option<i32>,
+    ypos:           Option<i32>,
 
     acontrol:       AudioControl,
     vcontrol:       VideoControl,
@@ -238,6 +320,7 @@ struct Player {
     tkeep:          TimeKeep,
 
     debug:          bool,
+    osd:            OSD,
 
     #[cfg(feature="debug")]
     logfile:        File,
@@ -253,6 +336,8 @@ impl Player {
         let vcontrol = VideoControl::new(None, 0, 0, 0, 0);
         Self {
             sdl_context, asystem, vsystem,
+            xpos:           None,
+            ypos:           None,
 
             acontrol, vcontrol,
 
@@ -274,12 +359,13 @@ impl Player {
             tkeep:          TimeKeep::new(),
 
             debug:          false,
+            osd:            OSD::new(),
 
             #[cfg(feature="debug")]
             logfile:        File::create("debug.log").unwrap(),
         }
     }
-    fn seek(&mut self, off: u64, fwd: bool, dmx: &mut Demuxer, disp_queue: &mut DispQueue) {
+    fn seek(&mut self, off: u64, fwd: bool, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> Result<(), ()> {
         let cur_time = self.tkeep.get_cur_time();
         let seektime = if fwd { cur_time + off * 1000 } else {
                 cur_time.saturating_sub(off * 1000) };
@@ -288,7 +374,7 @@ impl Player {
         let ret = dmx.seek(NATimePoint::Milliseconds(seektime));
         if ret.is_err() {
             println!(" seek error");
-            return;
+            return Ok(()); //TODO: not ignore some of seek errors?
         }
 
         self.acontrol.flush();
@@ -296,7 +382,7 @@ impl Player {
         disp_queue.flush();
 
         self.tkeep.reset_ts();
-        self.prefill(dmx, disp_queue);
+        self.prefill(dmx, disp_queue)?;
         if !disp_queue.is_empty() {
             self.tkeep.reset_all(disp_queue.first_ts);
         } else {
@@ -315,8 +401,9 @@ impl Player {
         if !self.paused {
             self.acontrol.resume();
         }
+        Ok(())
     }
-    fn prefill(&mut self, dmx: &mut Demuxer, disp_queue: &mut DispQueue) {
+    fn prefill(&mut self, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> Result<(), ()> {
         debug_log!(self; {" prefilling"});
         while self.vcontrol.get_queue_size() < FRAME_QUEUE_LEN {
             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()));
@@ -353,52 +440,57 @@ impl Player {
                 self.vcontrol.fill(disp_queue);
                 std::thread::sleep(Duration::from_millis(10));
             }
-            self.vcontrol.wait_for_frames();
+            self.vcontrol.wait_for_frames()?;
             self.vcontrol.fill(disp_queue);
         }
         debug_log!(self; {format!(" prefilling done, frames {}-{} audio {}", disp_queue.start, disp_queue.end, self.acontrol.get_fill())});
+        Ok(())
     }
-    fn handle_events(&mut self, event_pump: &mut sdl2::EventPump, canvas: &mut Canvas<Window>, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> bool {
+    fn toggle_pause(&mut self) {
+        self.paused = !self.paused;
+        if self.paused {
+            self.vsystem.enable_screen_saver();
+            self.tkeep.set_ts();
+        } else {
+            self.vsystem.disable_screen_saver();
+            self.tkeep.set_time();
+        }
+        if self.paused {
+            self.acontrol.pause();
+        } else {
+            self.acontrol.resume();
+        }
+    }
+    fn handle_events(&mut self, event_pump: &mut sdl2::EventPump, canvas: &mut Canvas<Window>, dmx: &mut Demuxer, disp_queue: &mut DispQueue) -> Result<bool, ()> {
         for event in event_pump.poll_iter() {
             if let Event::Quit {..} = event {
                 self.end = true;
                 println!();
-                return true;
+                return Ok(true);
             }
             if let Event::Window {win_event: WindowEvent::Exposed, ..} = event {
                 canvas.clear();
-                canvas.copy(disp_queue.get_last_texture(), None, None).unwrap();
+                canvas.copy(disp_queue.get_last_texture(&self.osd), None, None).unwrap();
                 canvas.present();
             }
+            if let Event::MouseButtonDown {mouse_btn: MouseButton::Right, ..} = event {
+                self.toggle_pause();
+            }
             if let Event::KeyDown {keycode: Some(keycode), ..} = event {
                 match keycode {
                     Keycode::Escape | Keycode::Q => {
                         self.end = true;
                         println!();
-                        return true;
-                    },
-                    Keycode::Return => return true,
-                    Keycode::Right      => { self.seek(10, true,  dmx, disp_queue); },
-                    Keycode::Left       => { self.seek(10, false, dmx, disp_queue); },
-                    Keycode::Up         => { self.seek(60, true,  dmx, disp_queue); },
-                    Keycode::Down       => { self.seek(60, false, dmx, disp_queue); },
-                    Keycode::PageUp     => { self.seek(600, true,  dmx, disp_queue); },
-                    Keycode::PageDown   => { self.seek(600, false, dmx, disp_queue); },
-                    Keycode::Space => {
-                        self.paused = !self.paused;
-                        if self.paused {
-                            self.vsystem.enable_screen_saver();
-                            self.tkeep.set_ts();
-                        } else {
-                            self.vsystem.disable_screen_saver();
-                            self.tkeep.set_time();
-                        }
-                        if self.paused {
-                            self.acontrol.pause();
-                        } else {
-                            self.acontrol.resume();
-                        }
+                        return Ok(true);
                     },
+                    Keycode::Return => return Ok(true),
+                    Keycode::Right      => { self.seek(10, true,  dmx, disp_queue)?; },
+                    Keycode::Left       => { self.seek(10, false, dmx, disp_queue)?; },
+                    Keycode::Up         => { self.seek(60, true,  dmx, disp_queue)?; },
+                    Keycode::Down       => { self.seek(60, false, dmx, disp_queue)?; },
+                    Keycode::PageUp     => { self.seek(600, true,  dmx, disp_queue)?; },
+                    Keycode::PageDown   => { self.seek(600, false, dmx, disp_queue)?; },
+                    Keycode::Space => { self.toggle_pause(); },
                     Keycode::Plus | Keycode::KpPlus => {
                         self.volume = (self.volume + 10).min(MAX_VOLUME);
                         if !self.mute {
@@ -425,6 +517,9 @@ impl Player {
                     Keycode::H => {
                         self.vcontrol.try_send_video(PktSendEvent::HurryUp);
                     },
+                    Keycode::O => {
+                        self.osd.toggle();
+                    },
                     _ => {},
                 };
                 if !self.paused {
@@ -433,7 +528,7 @@ impl Player {
                 }
             }
         }
-        false
+        Ok(false)
     }
     fn play(&mut self, name: &str, start_time: NATimePoint) {
         debug_log!(self; {format!("Playing {}", name)});
@@ -497,6 +592,8 @@ impl Player {
         }
         self.has_video = false;
         self.has_audio = false;
+        self.osd.reset();
+        self.osd.set_duration(duration);
         for i in 0..dmx.get_num_streams() {
             let s = dmx.get_stream(i).unwrap();
             let info = s.get_info();
@@ -604,14 +701,18 @@ impl Player {
             } else {
                 "NihAV player".to_owned()
             };
-        let window = self.vsystem.window(&wname, width as u32, height as u32)
-            .position_centered().build().unwrap();
+        let mut builder = self.vsystem.window(&wname, width as u32, height as u32);
+        let window = if let (Some(xpos), Some(ypos)) = (self.xpos, self.ypos) {
+                builder.position(xpos, ypos).build().unwrap()
+            } else {
+                builder.position_centered().build().unwrap()
+            };
         let mut canvas = window.into_canvas().build().unwrap();
         let texture_creator = canvas.texture_creator();
         let mut disp_q = DispQueue::new(&texture_creator, width, height, if self.has_video { FRAME_QUEUE_LEN } else { 0 });
         if !self.has_video {
             canvas.clear();
-            canvas.copy(disp_q.get_last_texture(), None, None).unwrap();
+            canvas.copy(disp_q.get_last_texture(&self.osd), None, None).unwrap();
             canvas.present();
         }
 
@@ -622,8 +723,14 @@ impl Player {
         }
 
         // play
-        self.prefill(&mut dmx, &mut disp_q);
-        self.tkeep.reset_all(0);
+        if self.prefill(&mut dmx, &mut disp_q).is_err() {
+            std::mem::swap(&mut self.vcontrol, &mut new_vcontrol);
+            new_vcontrol.finish();
+            std::mem::swap(&mut self.acontrol, &mut new_acontrol);
+            new_acontrol.finish();
+            return;
+        }
+        self.tkeep.reset_all(if !disp_q.is_empty() { disp_q.first_ts } else { 0 });
         if !self.paused {
             self.acontrol.resume();
         }
@@ -631,7 +738,8 @@ impl Player {
         let mut last_disp = Instant::now();
         let mut has_data = true;
         'main: loop {
-            if self.handle_events(&mut event_pump, &mut canvas, &mut dmx, &mut disp_q) {
+            let ret = self.handle_events(&mut event_pump, &mut canvas, &mut dmx, &mut disp_q);
+            if matches!(ret, Ok(true) | Err(_)) {
                 println!();
                 break 'main;
             }
@@ -650,7 +758,14 @@ impl Player {
                             self.acontrol.try_send_audio(PktSendEvent::End);
                             has_data = false;
                         },
-                        Err(err) => { println!("demuxer error {:?}", err); },
+                        Err(err) => {
+                            println!("demuxer error {:?}", err);
+                            if err == DemuxerError::IOError {
+                                self.vcontrol.try_send_video(PktSendEvent::End);
+                                self.acontrol.try_send_audio(PktSendEvent::End);
+                                has_data = false;
+                            }
+                        },
                         Ok(pkt) => {
                             let streamno = pkt.get_stream().get_id();
                             if self.has_video && streamno == self.video_str {
@@ -674,7 +789,7 @@ impl Player {
                 debug_log!(self; {format!(" time {}", self.tkeep.get_cur_time())});
                 if self.has_video {
                     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())});
-                    let ret = try_display(&mut disp_q, &mut canvas, &self.tkeep);
+                    let ret = try_display(&mut disp_q, &mut canvas, &mut self.osd, &self.tkeep);
                     if let Some(next_time) = ret {
                         sleep_time = sleep_time.min(next_time);
                     }
@@ -711,6 +826,9 @@ impl Player {
                 thread::sleep(Duration::from_millis(20));
             }
         }
+        let (xpos, ypos) = canvas.into_window().position();
+        self.xpos = Some(xpos);
+        self.ypos = Some(ypos);
         println!();
         std::mem::swap(&mut self.vcontrol, &mut new_vcontrol);
         new_vcontrol.finish();