]>
Commit | Line | Data |
---|---|---|
b043bd0a KS |
1 | extern crate libc; |
2 | extern crate sdl2_sys; | |
3 | extern crate nihav_core; | |
4 | extern crate nihav_registry; | |
b043bd0a KS |
5 | |
6 | use std::fs::File; | |
7 | use std::io::prelude::*; | |
8 | use std::io::{BufReader, SeekFrom}; | |
9 | use std::sync::mpsc; | |
10 | use std::time::Duration; | |
11 | use std::thread; | |
12 | use sdl2_sys::{SDL_AudioSpec, Uint32}; | |
13 | use nihav_core::io::byteio::{FileReader, ByteReader}; | |
14 | use nihav_core::frame::*; | |
15 | use nihav_core::codecs::*; | |
16 | use nihav_core::demuxers::*; | |
17 | use nihav_core::soundcvt::*; | |
18 | use nihav_registry::detect; | |
b043bd0a | 19 | |
41e41352 | 20 | mod allreg; |
b043bd0a KS |
21 | mod command; |
22 | use command::*; | |
23 | ||
24 | struct Player { | |
25 | ended: bool, | |
26 | dmx_reg: RegisteredDemuxers, | |
27 | dec_reg: RegisteredDecoders, | |
28 | paused: bool, | |
29 | mute: bool, | |
30 | volume: u8, | |
31 | debug: bool, | |
32 | buf: Vec<i16>, | |
33 | } | |
34 | ||
35 | struct AudioDevice { | |
36 | device_id: sdl2_sys::SDL_AudioDeviceID | |
37 | } | |
38 | ||
39 | impl AudioDevice { | |
40 | fn open(arate: u32, channels: u8) -> Option<(Self, SDL_AudioSpec)> { | |
41 | let desired_spec = SDL_AudioSpec { | |
42 | freq: arate as i32, | |
43 | format: sdl2_sys::AUDIO_S16 as u16, | |
44 | channels, | |
45 | silence: 0, | |
46 | samples: 0, | |
47 | padding: 0, | |
48 | size: 0, | |
49 | callback: None, | |
50 | userdata: std::ptr::null_mut(), | |
51 | }; | |
52 | let mut dspec = desired_spec; | |
53 | let device_id = unsafe { sdl2_sys::SDL_OpenAudioDevice(std::ptr::null(), 0, &desired_spec, &mut dspec, 0) }; | |
54 | if device_id != 0 { | |
55 | Some((AudioDevice { device_id }, dspec)) | |
56 | } else { | |
57 | None | |
58 | } | |
59 | } | |
60 | fn pause(&self) { | |
61 | unsafe { sdl2_sys::SDL_PauseAudioDevice(self.device_id, 1); } | |
62 | } | |
63 | fn resume(&self) { | |
64 | unsafe { sdl2_sys::SDL_PauseAudioDevice(self.device_id, 0); } | |
65 | } | |
66 | fn clear(&self) { | |
67 | unsafe { sdl2_sys::SDL_ClearQueuedAudio(self.device_id); } | |
68 | } | |
69 | fn queue(&self, buf: &[i16]) { | |
70 | unsafe { | |
71 | let len = buf.len() * 2; | |
72 | let buf_ptr = buf.as_ptr(); | |
73 | sdl2_sys::SDL_QueueAudio(self.device_id, buf_ptr as *const core::ffi::c_void, len as Uint32); | |
74 | } | |
75 | } | |
76 | fn queue_bytes(&self, buf: &[u8]) { | |
77 | unsafe { | |
78 | let len = buf.len(); | |
79 | let buf_ptr = buf.as_ptr(); | |
80 | sdl2_sys::SDL_QueueAudio(self.device_id, buf_ptr as *const core::ffi::c_void, len as Uint32); | |
81 | } | |
82 | } | |
83 | fn size(&self) -> u32 { | |
84 | unsafe { sdl2_sys::SDL_GetQueuedAudioSize(self.device_id) } | |
85 | } | |
86 | } | |
87 | ||
65606213 KS |
88 | impl Drop for AudioDevice { |
89 | fn drop(&mut self) { | |
90 | unsafe { sdl2_sys::SDL_CloseAudioDevice(self.device_id); } | |
91 | } | |
92 | } | |
93 | ||
b043bd0a KS |
94 | struct Decoder<'a> { |
95 | demuxer: Demuxer<'a>, | |
96 | decoder: Box<dyn NADecoder>, | |
97 | dsupp: Box<NADecoderSupport>, | |
98 | buf: &'a mut Vec<i16>, | |
99 | stream_no: usize, | |
100 | dst_info: NAAudioInfo, | |
101 | dst_chmap: NAChannelMap, | |
102 | samplepos: u64, | |
103 | arate: u32, | |
104 | volume: u8, | |
105 | mute: bool, | |
106 | } | |
107 | ||
108 | fn output_vol_i16(device: &AudioDevice, tmp: &mut Vec<i16>, src: &[i16], mute: bool, volume: u8) { | |
109 | if !mute { | |
110 | tmp.truncate(0); | |
111 | tmp.reserve(src.len()); | |
112 | let vol = i32::from(volume); | |
113 | for &sample in src.iter() { | |
114 | let nsamp = vol * i32::from(sample) / 100; | |
115 | tmp.push(nsamp.min(32767).max(-32768) as i16); | |
116 | } | |
117 | } else { | |
118 | tmp.truncate(0); | |
119 | tmp.resize(src.len(), 0); | |
120 | } | |
121 | device.queue(&tmp); | |
122 | } | |
123 | ||
124 | fn output_vol_u8(device: &AudioDevice, tmp: &mut Vec<i16>, src: &[u8], mute: bool, volume: u8) { | |
125 | if !mute { | |
126 | tmp.truncate(0); | |
127 | tmp.reserve(src.len()); | |
128 | let vol = i32::from(volume); | |
129 | for sample in src.chunks_exact(2) { | |
130 | let sample = (u16::from(sample[0]) + u16::from(sample[1]) * 256) as i16; | |
131 | let nsamp = vol * i32::from(sample) / 100; | |
132 | tmp.push(nsamp.min(32767).max(-32768) as i16); | |
133 | } | |
134 | } else { | |
135 | tmp.truncate(0); | |
136 | tmp.resize(src.len() / 2, 0); | |
137 | } | |
138 | device.queue(&tmp); | |
139 | } | |
140 | ||
141 | impl<'a> Decoder<'a> { | |
142 | fn refill(&mut self, device: &AudioDevice) -> bool { | |
143 | loop { | |
144 | match self.demuxer.get_frame() { | |
145 | Ok(pkt) => { | |
146 | if pkt.get_stream().get_num() == self.stream_no { | |
147 | match self.decoder.decode(&mut self.dsupp, &pkt) { | |
148 | Ok(frm) => { | |
149 | let buf = frm.get_buffer(); | |
89f53245 KS |
150 | if let NABufferType::None = buf { |
151 | return false; | |
152 | } | |
b043bd0a KS |
153 | if let Some(pts) = frm.ts.get_pts() { |
154 | self.samplepos = NATimeInfo::ts_to_time(pts, u64::from(self.arate), frm.ts.tb_num, frm.ts.tb_den); | |
155 | } | |
156 | let out_buf = convert_audio_frame(&buf, &self.dst_info, &self.dst_chmap).unwrap(); | |
157 | match out_buf { | |
158 | NABufferType::AudioI16(abuf) => { | |
159 | if !self.mute && self.volume == 100 { | |
160 | device.queue(&abuf.get_data()); | |
161 | } else { | |
162 | output_vol_i16(device, self.buf, &abuf.get_data(), self.mute, self.volume); | |
163 | } | |
164 | self.samplepos += abuf.get_length() as u64; | |
165 | }, | |
166 | NABufferType::AudioPacked(abuf) => { | |
167 | if !self.mute && self.volume == 100 { | |
168 | device.queue_bytes(&abuf.get_data()); | |
169 | } else { | |
170 | output_vol_u8(device, self.buf, &abuf.get_data(), self.mute, self.volume); | |
171 | } | |
172 | self.samplepos += abuf.get_length() as u64; | |
173 | }, | |
174 | _ => println!("unknown buffer type"), | |
175 | }; | |
176 | return false; | |
177 | }, | |
178 | Err(err) => { | |
179 | println!(" error decoding {:?}", err); | |
180 | return true; | |
181 | }, | |
182 | }; | |
183 | } | |
184 | }, | |
185 | Err(DemuxerError::EOF) => return true, | |
186 | Err(err) => { | |
187 | println!("demuxing error {:?}", err); | |
188 | return true; | |
189 | }, | |
190 | }; | |
191 | } | |
192 | } | |
193 | fn seek(&mut self, time: u64) -> bool { | |
194 | let ret = self.demuxer.seek(NATimePoint::Milliseconds(time)); | |
195 | if ret.is_err() { println!(" seek error\n"); } | |
196 | ret.is_ok() | |
197 | } | |
198 | } | |
199 | ||
200 | fn format_time(ms: u64) -> String { | |
201 | let s = ms / 1000; | |
202 | let ds = (ms % 1000) / 100; | |
203 | let (min, s) = (s / 60, s % 60); | |
be48734e KS |
204 | let (h, min) = (min / 60, min % 60); |
205 | if h == 0 { | |
206 | if min == 0 { | |
207 | format!("{}.{}", s, ds) | |
208 | } else { | |
209 | format!("{}:{:02}.{}", min, s, ds) | |
210 | } | |
b043bd0a | 211 | } else { |
be48734e | 212 | format!("{}:{:02}:{:02}.{}", h, min, s, ds) |
b043bd0a KS |
213 | } |
214 | } | |
215 | ||
216 | impl Player { | |
217 | fn new() -> Self { | |
218 | let mut dmx_reg = RegisteredDemuxers::new(); | |
41e41352 | 219 | allreg::register_all_demuxers(&mut dmx_reg); |
b043bd0a | 220 | let mut dec_reg = RegisteredDecoders::new(); |
41e41352 | 221 | allreg::register_all_decoders(&mut dec_reg); |
b043bd0a KS |
222 | |
223 | unsafe { | |
224 | if sdl2_sys::SDL_Init(sdl2_sys::SDL_INIT_AUDIO) != 0 { | |
225 | panic!("cannot init SDL"); | |
226 | } | |
227 | } | |
228 | ||
229 | Self { | |
230 | ended: false, | |
231 | paused: false, | |
232 | dmx_reg, dec_reg, | |
233 | volume: 100, | |
234 | mute: false, | |
235 | debug: false, | |
236 | buf: Vec::new(), | |
237 | } | |
238 | } | |
36ac48e5 | 239 | fn play_file(&mut self, name: &str, cmd_receiver: &mpsc::Receiver<Command>, start_time: NATimePoint) { |
b043bd0a KS |
240 | let ret = File::open(name); |
241 | if ret.is_err() { | |
242 | println!("error opening {}", name); | |
243 | return; | |
244 | } | |
245 | let mut file = ret.unwrap(); | |
246 | ||
247 | let mut fr = FileReader::new_read(&mut file); | |
248 | let mut br = ByteReader::new(&mut fr); | |
249 | let res = detect::detect_format(name, &mut br); | |
250 | if res.is_none() { | |
251 | println!("cannot detect format for {}", name); | |
252 | return; | |
253 | } | |
254 | let (dmx_name, _) = res.unwrap(); | |
255 | drop(br); | |
256 | drop(fr); | |
257 | let dmx_fact = self.dmx_reg.find_demuxer(dmx_name); | |
258 | if dmx_fact.is_none() { | |
259 | println!("no demuxer for format {}", dmx_name); | |
260 | return; | |
261 | } | |
262 | let dmx_fact = dmx_fact.unwrap(); | |
263 | ||
264 | file.seek(SeekFrom::Start(0)).unwrap(); | |
265 | let mut file = BufReader::new(file); | |
266 | let mut fr = FileReader::new_read(&mut file); | |
267 | let mut br = ByteReader::new(&mut fr); | |
268 | let res = create_demuxer(dmx_fact, &mut br); | |
269 | if res.is_err() { | |
270 | println!("cannot create demuxer"); | |
271 | return; | |
272 | } | |
273 | let dmx = res.unwrap(); | |
274 | ||
275 | let mut ainfo = None; | |
276 | let mut dec: Option<(Box<NADecoderSupport>, Box<dyn NADecoder>)> = None; | |
277 | let mut stream_no = 0; | |
278 | let mut duration = dmx.get_duration(); | |
279 | for i in 0..dmx.get_num_streams() { | |
280 | let s = dmx.get_stream(i).unwrap(); | |
281 | let info = s.get_info(); | |
282 | if info.is_audio() { | |
283 | let decfunc = self.dec_reg.find_decoder(info.get_name()); | |
284 | if decfunc.is_none() { | |
285 | println!("no decoder for {}", info.get_name()); | |
286 | continue; | |
287 | } | |
288 | let mut decoder = (decfunc.unwrap())(); | |
289 | let mut dsupp = Box::new(NADecoderSupport::new()); | |
290 | if decoder.init(&mut dsupp, info.clone()).is_err() { | |
291 | println!("cannot init decoder for stream {}", i); | |
292 | continue; | |
293 | } | |
294 | dec = Some((dsupp, decoder)); | |
295 | ainfo = Some(info); | |
296 | stream_no = i; | |
297 | if s.duration > 0 { | |
298 | duration = NATimeInfo::ts_to_time(s.duration, 1000, s.tb_num, s.tb_den); | |
299 | } | |
300 | break; | |
301 | } | |
302 | } | |
303 | if dec.is_none() { | |
304 | println!("no audio decoder found"); | |
305 | return; | |
306 | } | |
307 | let (dsupp, decoder) = dec.unwrap(); | |
308 | ||
309 | let ainfo = ainfo.unwrap().get_properties().get_audio_info().unwrap(); | |
310 | let arate = if ainfo.sample_rate > 0 { ainfo.sample_rate } else { 44100 }; | |
311 | let ch = ainfo.channels; | |
312 | ||
313 | println!("Playing {} [{}Hz {}ch]", name, arate, ch); | |
5a92ee55 | 314 | let ret = AudioDevice::open(arate, ch.min(2)); |
b043bd0a KS |
315 | if ret.is_none() { |
316 | println!("cannot open output"); | |
317 | return; | |
318 | } | |
319 | let (device, dspec) = ret.unwrap(); | |
320 | let block_limit = dmx.get_stream(stream_no).unwrap().tb_num * arate / dmx.get_stream(stream_no).unwrap().tb_den * u32::from(dspec.channels); | |
321 | ||
322 | let dst_info = NAAudioInfo { sample_rate: dspec.freq as u32, channels: dspec.channels, format: SND_S16_FORMAT, block_len: 1 }; | |
323 | let dst_chmap = if dst_info.channels == 2 { | |
324 | NAChannelMap::from_str("L,R").unwrap() | |
325 | } else { | |
326 | NAChannelMap::from_str("C").unwrap() | |
327 | }; | |
328 | let mut decoder = Decoder { | |
329 | demuxer: dmx, | |
330 | decoder, dsupp, | |
331 | stream_no, | |
332 | dst_info, dst_chmap, | |
333 | samplepos: 0, | |
334 | arate, | |
335 | volume: self.volume, | |
336 | mute: self.mute, | |
337 | buf: &mut self.buf, | |
338 | }; | |
339 | let mut refill_limit = arate * u32::from(dspec.channels); | |
340 | let underfill_limit = (arate * u32::from(dspec.channels) / 4).max(block_limit); | |
341 | ||
36ac48e5 KS |
342 | if start_time != NATimePoint::None { |
343 | let _ret = decoder.demuxer.seek(start_time); | |
344 | } | |
345 | ||
b043bd0a KS |
346 | let mut eof = decoder.refill(&device); |
347 | while !eof && device.size() < refill_limit { | |
348 | eof = decoder.refill(&device); | |
349 | } | |
350 | ||
351 | let duration_str = if duration != 0 { format_time(duration) } else { "???".to_owned() }; | |
352 | if !self.paused { | |
353 | device.resume(); | |
354 | } | |
0a727e05 | 355 | let mut no_display = false; |
b043bd0a KS |
356 | 'main: loop { |
357 | let cur_time = decoder.samplepos.saturating_sub(u64::from(device.size() / 2 / u32::from(dst_info.channels))); | |
358 | let full_ms = cur_time * 1000 / u64::from(arate); | |
359 | let timestr = format_time(full_ms); | |
360 | let disp_vol = if self.mute { 0 } else { self.volume }; | |
0a727e05 KS |
361 | if !no_display { |
362 | if !self.debug { | |
363 | print!("> {} / {} {}% \r", timestr, duration_str, disp_vol); | |
364 | } else { | |
365 | print!("> {} / {} |{}| {}% \r", timestr, duration_str, device.size(), disp_vol); | |
366 | } | |
b043bd0a KS |
367 | } |
368 | std::io::stdout().flush().unwrap(); | |
369 | if device.size() < underfill_limit && !self.paused && refill_limit < (1 << 20) { | |
370 | if full_ms > 5000 { | |
371 | println!("underrun!"); | |
372 | } | |
373 | refill_limit += refill_limit >> 1; | |
374 | } | |
375 | if device.size() < refill_limit / 2 { | |
376 | while !eof && device.size() < refill_limit { | |
377 | eof = decoder.refill(&device); | |
378 | } | |
379 | } | |
380 | if eof && device.size() == 0 { | |
381 | break 'main; | |
382 | } | |
383 | while let Ok(cmd) = cmd_receiver.try_recv() { | |
384 | let cur_time = decoder.samplepos.saturating_sub(u64::from(device.size() / 2 / u32::from(dst_info.channels))); | |
385 | match cmd { | |
386 | Command::Forward(val) => { | |
387 | device.pause(); | |
388 | device.clear(); | |
389 | let seekoff = match val { | |
390 | 1 => 10, | |
391 | 2 => 60, | |
392 | _ => 10 * 60, | |
393 | }; | |
394 | let seek_time = cur_time * 1000 / u64::from(arate) + seekoff * 1000; | |
395 | let _ret = decoder.seek(seek_time); | |
396 | while !eof && device.size() < refill_limit { | |
397 | eof = decoder.refill(&device); | |
398 | } | |
399 | if eof { | |
400 | break 'main; | |
401 | } | |
402 | if !self.paused { | |
403 | device.resume(); | |
404 | } | |
405 | }, | |
406 | Command::Back(val) => { | |
407 | device.pause(); | |
408 | device.clear(); | |
409 | let seekoff = match val { | |
410 | 1 => 10, | |
411 | 2 => 60, | |
412 | _ => 10 * 60, | |
413 | }; | |
414 | let seek_time = (cur_time * 1000 / u64::from(arate)).saturating_sub(seekoff * 1000); | |
415 | let _ret = decoder.seek(seek_time); | |
416 | while !eof && device.size() < refill_limit { | |
417 | eof = decoder.refill(&device); | |
418 | } | |
419 | if eof { | |
420 | break 'main; | |
421 | } | |
422 | if !self.paused { | |
423 | device.resume(); | |
424 | } | |
425 | }, | |
0a727e05 KS |
426 | Command::Seek(seek_time) => { |
427 | device.pause(); | |
428 | device.clear(); | |
429 | let _ret = decoder.seek(seek_time); | |
430 | while !eof && device.size() < refill_limit { | |
431 | eof = decoder.refill(&device); | |
432 | } | |
433 | if eof { | |
434 | break 'main; | |
435 | } | |
436 | if !self.paused { | |
437 | device.resume(); | |
438 | } | |
439 | }, | |
b043bd0a KS |
440 | Command::Quit => { |
441 | device.pause(); | |
442 | self.ended = true; | |
443 | break 'main; | |
444 | }, | |
445 | Command::Next => { | |
446 | device.pause(); | |
447 | break 'main; | |
448 | }, | |
449 | Command::Repeat => { | |
450 | device.pause(); | |
451 | device.clear(); | |
452 | let _ret = decoder.seek(0); | |
453 | while !eof && device.size() < refill_limit { | |
454 | eof = decoder.refill(&device); | |
455 | } | |
456 | if eof { | |
457 | break 'main; | |
458 | } | |
459 | if !self.paused { | |
460 | device.resume(); | |
461 | } | |
462 | }, | |
463 | Command::Pause => { | |
464 | self.paused = !self.paused; | |
465 | if self.paused { | |
466 | device.pause(); | |
467 | } else { | |
468 | device.resume(); | |
469 | } | |
470 | }, | |
471 | Command::VolumeUp => { | |
472 | self.volume = (self.volume + 10).min(200); | |
473 | decoder.volume = self.volume; | |
474 | }, | |
475 | Command::VolumeDown => { | |
476 | self.volume = self.volume.saturating_sub(10); | |
477 | decoder.volume = self.volume; | |
478 | }, | |
479 | Command::Mute => { | |
480 | self.mute = !self.mute; | |
481 | decoder.mute = self.mute; | |
482 | }, | |
483 | Command::Debug => { | |
484 | self.debug = !self.debug; | |
485 | }, | |
98431ed5 KS |
486 | Command::PauseDisplay => { |
487 | no_display = true; | |
488 | if !self.paused { | |
489 | device.pause(); | |
490 | } | |
491 | }, | |
492 | Command::ResumeDisplay => { | |
493 | no_display = false; | |
494 | if !self.paused { | |
495 | device.resume(); | |
496 | } | |
497 | }, | |
b043bd0a | 498 | }; |
0a727e05 KS |
499 | if !no_display { |
500 | print!("\r{:60}\r", ' '); | |
501 | } | |
b043bd0a KS |
502 | } |
503 | thread::sleep(Duration::from_millis(200)); | |
504 | } | |
505 | ||
506 | println!(); | |
507 | } | |
508 | } | |
509 | ||
510 | fn main() { | |
511 | let args: Vec<String> = std::env::args().collect(); | |
512 | ||
513 | let cmd_state = CmdLineState::new(); | |
514 | ||
515 | let (cmd_reader_thread, cmd_receiver) = start_reader(); | |
516 | let mut player = Player::new(); | |
517 | ||
518 | if args.len() == 1 { | |
519 | println!("usage: nihav-sndplay file1 file2 ..."); | |
520 | return; | |
521 | } | |
522 | ||
523 | if args[1] == "--help" { | |
524 | println!("usage: nihav-sndplay file1 file2 ..."); | |
525 | println!("commands:"); | |
526 | println!(" escape / q - quit"); | |
527 | println!(" space - pause / resume playback"); | |
528 | println!(" enter / end - play next file"); | |
529 | println!(" home - play current track from the beginning"); | |
530 | println!(" left / right - seek 10 seconds forward / back"); | |
531 | println!(" up / down - seek 1 minute forward / back"); | |
532 | println!(" pgup / pgdn - seek 10 minutes forward / back"); | |
533 | println!(" + / - - increase / decrease volume"); | |
534 | println!(" m - mute output"); | |
535 | return; | |
536 | } | |
537 | ||
36ac48e5 KS |
538 | let mut aiter = args[1..].iter(); |
539 | let mut start_time = NATimePoint::None; | |
540 | while let Some(arg) = aiter.next() { | |
541 | match arg.as_str() { | |
542 | "-start" => { | |
543 | if let Some(arg) = aiter.next() { | |
544 | if let Ok(val) = arg.parse() { | |
545 | start_time = val; | |
546 | } else { | |
547 | println!("invalid time"); | |
548 | } | |
549 | } else { | |
550 | println!("argument is required"); | |
551 | } | |
552 | }, | |
553 | _ => { | |
554 | player.play_file(arg, &cmd_receiver, start_time); | |
555 | if player.ended { | |
556 | break; | |
557 | } | |
558 | start_time = NATimePoint::None; | |
559 | }, | |
560 | }; | |
b043bd0a KS |
561 | } |
562 | cmd_state.restore(); | |
563 | ||
564 | drop(cmd_reader_thread); | |
565 | unsafe { sdl2_sys::SDL_Quit(); } | |
566 | } |