add nihav-sndplay
[nihav-player.git] / sndplay / src / main.rs
diff --git a/sndplay/src/main.rs b/sndplay/src/main.rs
new file mode 100644 (file)
index 0000000..66def2b
--- /dev/null
@@ -0,0 +1,500 @@
+extern crate libc;
+extern crate sdl2_sys;
+extern crate nihav_core;
+extern crate nihav_registry;
+extern crate nihav_allstuff;
+
+use std::fs::File;
+use std::io::prelude::*;
+use std::io::{BufReader, SeekFrom};
+use std::sync::mpsc;
+use std::time::Duration;
+use std::thread;
+use sdl2_sys::{SDL_AudioSpec, Uint32};
+use nihav_core::io::byteio::{FileReader, ByteReader};
+use nihav_core::frame::*;
+use nihav_core::codecs::*;
+use nihav_core::demuxers::*;
+use nihav_core::soundcvt::*;
+use nihav_registry::detect;
+use nihav_allstuff::*;
+
+mod command;
+use command::*;
+
+struct Player {
+    ended:      bool,
+    dmx_reg:    RegisteredDemuxers,
+    dec_reg:    RegisteredDecoders,
+    paused:     bool,
+    mute:       bool,
+    volume:     u8,
+    debug:      bool,
+    buf:        Vec<i16>,
+}
+
+struct AudioDevice {
+    device_id:  sdl2_sys::SDL_AudioDeviceID
+}
+
+impl AudioDevice {
+    fn open(arate: u32, channels: u8) -> Option<(Self, SDL_AudioSpec)> {
+        let desired_spec = SDL_AudioSpec {
+            freq:       arate as i32,
+            format:     sdl2_sys::AUDIO_S16 as u16,
+            channels,
+            silence:    0,
+            samples:    0,
+            padding:    0,
+            size:       0,
+            callback:   None,
+            userdata:   std::ptr::null_mut(),
+        };
+        let mut dspec = desired_spec;
+        let device_id = unsafe { sdl2_sys::SDL_OpenAudioDevice(std::ptr::null(), 0, &desired_spec, &mut dspec, 0) };
+        if device_id != 0 {
+            Some((AudioDevice { device_id }, dspec))
+        } else {
+            None
+        }
+    }
+    fn pause(&self) {
+        unsafe { sdl2_sys::SDL_PauseAudioDevice(self.device_id, 1); }
+    }
+    fn resume(&self) {
+        unsafe { sdl2_sys::SDL_PauseAudioDevice(self.device_id, 0); }
+    }
+    fn clear(&self) {
+        unsafe { sdl2_sys::SDL_ClearQueuedAudio(self.device_id); }
+    }
+    fn queue(&self, buf: &[i16]) {
+        unsafe {
+            let len = buf.len() * 2;
+            let buf_ptr = buf.as_ptr();
+            sdl2_sys::SDL_QueueAudio(self.device_id, buf_ptr as *const core::ffi::c_void, len as Uint32);
+        }
+    }
+    fn queue_bytes(&self, buf: &[u8]) {
+        unsafe {
+            let len = buf.len();
+            let buf_ptr = buf.as_ptr();
+            sdl2_sys::SDL_QueueAudio(self.device_id, buf_ptr as *const core::ffi::c_void, len as Uint32);
+        }
+    }
+    fn size(&self) -> u32 {
+        unsafe { sdl2_sys::SDL_GetQueuedAudioSize(self.device_id) }
+    }
+}
+
+struct Decoder<'a> {
+    demuxer:    Demuxer<'a>,
+    decoder:    Box<dyn NADecoder>,
+    dsupp:      Box<NADecoderSupport>,
+    buf:        &'a mut Vec<i16>,
+    stream_no:  usize,
+    dst_info:   NAAudioInfo,
+    dst_chmap:  NAChannelMap,
+    samplepos:  u64,
+    arate:      u32,
+    volume:     u8,
+    mute:       bool,
+}
+
+fn output_vol_i16(device: &AudioDevice, tmp: &mut Vec<i16>, src: &[i16], mute: bool, volume: u8) {
+    if !mute {
+        tmp.truncate(0);
+        tmp.reserve(src.len());
+        let vol = i32::from(volume);
+        for &sample in src.iter() {
+            let nsamp = vol * i32::from(sample) / 100;
+            tmp.push(nsamp.min(32767).max(-32768) as i16);
+        }
+    } else {
+        tmp.truncate(0);
+        tmp.resize(src.len(), 0);
+    }
+    device.queue(&tmp);
+}
+
+fn output_vol_u8(device: &AudioDevice, tmp: &mut Vec<i16>, src: &[u8], mute: bool, volume: u8) {
+    if !mute {
+        tmp.truncate(0);
+        tmp.reserve(src.len());
+        let vol = i32::from(volume);
+        for sample in src.chunks_exact(2) {
+            let sample = (u16::from(sample[0]) + u16::from(sample[1]) * 256) as i16;
+            let nsamp = vol * i32::from(sample) / 100;
+            tmp.push(nsamp.min(32767).max(-32768) as i16);
+        }
+    } else {
+        tmp.truncate(0);
+        tmp.resize(src.len() / 2, 0);
+    }
+    device.queue(&tmp);
+}
+
+impl<'a> Decoder<'a> {
+    fn refill(&mut self, device: &AudioDevice) -> bool {
+        loop {
+            match self.demuxer.get_frame() {
+                Ok(pkt) => {
+                    if pkt.get_stream().get_num() == self.stream_no {
+                        match self.decoder.decode(&mut self.dsupp, &pkt) {
+                            Ok(frm) => {
+                                let buf = frm.get_buffer();
+                                if let Some(pts) = frm.ts.get_pts() {
+                                    self.samplepos = NATimeInfo::ts_to_time(pts, u64::from(self.arate), frm.ts.tb_num, frm.ts.tb_den);
+                                }
+                                let out_buf = convert_audio_frame(&buf, &self.dst_info, &self.dst_chmap).unwrap();
+                                match out_buf {
+                                    NABufferType::AudioI16(abuf) => {
+                                        if !self.mute && self.volume == 100 {
+                                            device.queue(&abuf.get_data());
+                                        } else {
+                                            output_vol_i16(device, self.buf, &abuf.get_data(), self.mute, self.volume);
+                                        }
+                                        self.samplepos += abuf.get_length() as u64;
+                                    },
+                                    NABufferType::AudioPacked(abuf) => {
+                                        if !self.mute && self.volume == 100 {
+                                            device.queue_bytes(&abuf.get_data());
+                                        } else {
+                                            output_vol_u8(device, self.buf, &abuf.get_data(), self.mute, self.volume);
+                                        }
+                                        self.samplepos += abuf.get_length() as u64;
+                                    },
+                                    _ => println!("unknown buffer type"),
+                                };
+                                return false;
+                            },
+                            Err(err) => {
+                                println!(" error decoding {:?}", err);
+                                return true;
+                            },
+                        };
+                    }
+                },
+                Err(DemuxerError::EOF) => return true,
+                Err(err) => {
+                    println!("demuxing error {:?}", err);
+                    return true;
+                },
+            };
+        }
+    }
+    fn seek(&mut self, time: u64) -> bool {
+        let ret = self.demuxer.seek(NATimePoint::Milliseconds(time));
+if ret.is_err() { println!(" seek error\n"); }
+        ret.is_ok()
+    }
+}
+
+fn format_time(ms: u64) -> String {
+    let s = ms / 1000;
+    let ds = (ms % 1000) / 100;
+    let (min, s) = (s / 60, s % 60);
+    if min == 0 {
+        format!("{}.{}", s, ds)
+    } else {
+        format!("{}:{:02}.{}", min, s, ds)
+    }
+}
+
+impl Player {
+    fn new() -> Self {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        nihav_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        nihav_register_all_decoders(&mut dec_reg);
+
+        unsafe {
+            if sdl2_sys::SDL_Init(sdl2_sys::SDL_INIT_AUDIO) != 0 {
+                panic!("cannot init SDL");
+            }
+        }
+
+        Self {
+            ended:  false,
+            paused: false,
+            dmx_reg, dec_reg,
+            volume: 100,
+            mute:   false,
+            debug:  false,
+            buf:    Vec::new(),
+        }
+    }
+    fn play_file(&mut self, name: &str, cmd_receiver: &mpsc::Receiver<Command>) {
+        let ret = File::open(name);
+        if ret.is_err() {
+            println!("error opening {}", name);
+            return;
+        }
+        let mut file = ret.unwrap();
+
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let res = detect::detect_format(name, &mut br);
+        if res.is_none() {
+            println!("cannot detect format for {}", name);
+            return;
+        }
+        let (dmx_name, _) = res.unwrap();
+        drop(br);
+        drop(fr);
+        let dmx_fact = self.dmx_reg.find_demuxer(dmx_name);
+        if dmx_fact.is_none() {
+            println!("no demuxer for format {}", dmx_name);
+            return;
+        }
+        let dmx_fact = dmx_fact.unwrap();
+
+        file.seek(SeekFrom::Start(0)).unwrap();
+        let mut file = BufReader::new(file);
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let res = create_demuxer(dmx_fact, &mut br);
+        if res.is_err() {
+            println!("cannot create demuxer");
+            return;
+        }
+        let dmx = res.unwrap();
+
+        let mut ainfo = None;
+        let mut dec: Option<(Box<NADecoderSupport>, Box<dyn NADecoder>)> = None;
+        let mut stream_no = 0;
+        let mut duration = dmx.get_duration();
+        for i in 0..dmx.get_num_streams() {
+            let s = dmx.get_stream(i).unwrap();
+            let info = s.get_info();
+            if info.is_audio() {
+                let decfunc = self.dec_reg.find_decoder(info.get_name());
+                if decfunc.is_none() {
+                    println!("no decoder for {}", info.get_name());
+                    continue;
+                }
+                let mut decoder = (decfunc.unwrap())();
+                let mut dsupp = Box::new(NADecoderSupport::new());
+                if decoder.init(&mut dsupp, info.clone()).is_err() {
+                    println!("cannot init decoder for stream {}", i);
+                    continue;
+                }
+                dec = Some((dsupp, decoder));
+                ainfo = Some(info);
+                stream_no = i;
+                if s.duration > 0 {
+                    duration = NATimeInfo::ts_to_time(s.duration, 1000, s.tb_num, s.tb_den);
+                }
+                break;
+            }
+        }
+        if dec.is_none() {
+            println!("no audio decoder found");
+            return;
+        }
+        let (dsupp, decoder) = dec.unwrap();
+
+        let ainfo = ainfo.unwrap().get_properties().get_audio_info().unwrap();
+        let arate = if ainfo.sample_rate > 0 { ainfo.sample_rate } else { 44100 };
+        let ch    = ainfo.channels;
+
+        println!("Playing {} [{}Hz {}ch]", name, arate, ch);
+        let ret = AudioDevice::open(arate, ch.max(2));
+        if ret.is_none() {
+            println!("cannot open output");
+            return;
+        }
+        let (device, dspec) = ret.unwrap();
+        let block_limit = dmx.get_stream(stream_no).unwrap().tb_num * arate / dmx.get_stream(stream_no).unwrap().tb_den * u32::from(dspec.channels);
+
+        let dst_info = NAAudioInfo { sample_rate: dspec.freq as u32, channels: dspec.channels, format: SND_S16_FORMAT, block_len: 1 };
+        let dst_chmap = if dst_info.channels == 2 {
+                NAChannelMap::from_str("L,R").unwrap()
+            } else {
+                NAChannelMap::from_str("C").unwrap()
+            };
+        let mut decoder = Decoder {
+                demuxer:    dmx,
+                decoder, dsupp,
+                stream_no,
+                dst_info, dst_chmap,
+                samplepos: 0,
+                arate,
+                volume: self.volume,
+                mute:   self.mute,
+                buf:    &mut self.buf,
+            };
+        let mut refill_limit = arate * u32::from(dspec.channels);
+        let underfill_limit = (arate * u32::from(dspec.channels) / 4).max(block_limit);
+
+        let mut eof = decoder.refill(&device);
+        while !eof && device.size() < refill_limit {
+            eof = decoder.refill(&device);
+        }
+
+        let duration_str = if duration != 0 { format_time(duration) } else { "???".to_owned() };
+        if !self.paused {
+            device.resume();
+        }
+        'main: loop {
+            let cur_time = decoder.samplepos.saturating_sub(u64::from(device.size() / 2 / u32::from(dst_info.channels)));
+            let full_ms = cur_time * 1000 / u64::from(arate);
+            let timestr = format_time(full_ms);
+            let disp_vol = if self.mute { 0 } else { self.volume };
+            if !self.debug {
+                print!("> {} / {}   {}%       \r", timestr, duration_str, disp_vol);
+            } else {
+                print!("> {} / {}   |{}| {}%       \r", timestr, duration_str, device.size(), disp_vol);
+            }
+            std::io::stdout().flush().unwrap();
+            if device.size() < underfill_limit && !self.paused && refill_limit < (1 << 20) {
+                if full_ms > 5000 {
+                    println!("underrun!");
+                }
+                refill_limit += refill_limit >> 1;
+            }
+            if device.size() < refill_limit / 2 {
+                while !eof && device.size() < refill_limit {
+                    eof = decoder.refill(&device);
+                }
+            }
+            if eof && device.size() == 0 {
+                break 'main;
+            }
+            while let Ok(cmd) = cmd_receiver.try_recv() {
+                let cur_time = decoder.samplepos.saturating_sub(u64::from(device.size() / 2 / u32::from(dst_info.channels)));
+                match cmd {
+                    Command::Forward(val) => {
+                        device.pause();
+                        device.clear();
+                        let seekoff = match val {
+                                1 => 10,
+                                2 => 60,
+                                _ => 10 * 60,
+                            };
+                        let seek_time = cur_time * 1000 / u64::from(arate) + seekoff * 1000;
+                        let _ret = decoder.seek(seek_time);
+                        while !eof && device.size() < refill_limit {
+                            eof = decoder.refill(&device);
+                        }
+                        if eof {
+                            break 'main;
+                        }
+                        if !self.paused {
+                            device.resume();
+                        }
+                    },
+                    Command::Back(val) => {
+                        device.pause();
+                        device.clear();
+                        let seekoff = match val {
+                                1 => 10,
+                                2 => 60,
+                                _ => 10 * 60,
+                            };
+                        let seek_time = (cur_time * 1000 / u64::from(arate)).saturating_sub(seekoff * 1000);
+                        let _ret = decoder.seek(seek_time);
+                        while !eof && device.size() < refill_limit {
+                            eof = decoder.refill(&device);
+                        }
+                        if eof {
+                            break 'main;
+                        }
+                        if !self.paused {
+                            device.resume();
+                        }
+                    },
+                    Command::Quit => {
+                        device.pause();
+                        self.ended = true;
+                        break 'main;
+                    },
+                    Command::Next => {
+                        device.pause();
+                        break 'main;
+                    },
+                    Command::Repeat => {
+                        device.pause();
+                        device.clear();
+                        let _ret = decoder.seek(0);
+                        while !eof && device.size() < refill_limit {
+                            eof = decoder.refill(&device);
+                        }
+                        if eof {
+                            break 'main;
+                        }
+                        if !self.paused {
+                            device.resume();
+                        }
+                    },
+                    Command::Pause => {
+                        self.paused = !self.paused;
+                        if self.paused {
+                            device.pause();
+                        } else {
+                            device.resume();
+                        }
+                    },
+                    Command::VolumeUp => {
+                        self.volume = (self.volume + 10).min(200);
+                        decoder.volume = self.volume;
+                    },
+                    Command::VolumeDown => {
+                        self.volume = self.volume.saturating_sub(10);
+                        decoder.volume = self.volume;
+                    },
+                    Command::Mute => {
+                        self.mute = !self.mute;
+                        decoder.mute = self.mute;
+                    },
+                    Command::Debug => {
+                        self.debug = !self.debug;
+                    },
+                };
+                print!("\r{:60}\r", ' ');
+            }
+            thread::sleep(Duration::from_millis(200));
+        }
+
+        println!();
+    }
+}
+
+fn main() {
+    let args: Vec<String> = std::env::args().collect();
+
+    let cmd_state = CmdLineState::new();
+
+    let (cmd_reader_thread, cmd_receiver) = start_reader();
+    let mut player = Player::new();
+
+    if args.len() == 1 {
+        println!("usage: nihav-sndplay file1 file2 ...");
+        return;
+    }
+
+    if args[1] == "--help" {
+        println!("usage: nihav-sndplay file1 file2 ...");
+        println!("commands:");
+        println!("  escape / q      - quit");
+        println!("  space           - pause / resume playback");
+        println!("  enter / end     - play next file");
+        println!("  home            - play current track from the beginning");
+        println!("  left / right    - seek 10 seconds forward / back");
+        println!("  up / down       - seek 1 minute forward / back");
+        println!("  pgup / pgdn     - seek 10 minutes forward / back");
+        println!("  + / -           - increase / decrease volume");
+        println!("  m               - mute output");
+        return;
+    }
+
+    for arg in args[1..].iter() {
+        player.play_file(arg, &cmd_receiver);
+        if player.ended {
+            break;
+        }
+    }
+    cmd_state.restore();
+
+    drop(cmd_reader_thread);
+    unsafe { sdl2_sys::SDL_Quit(); }
+}