-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathac3_decode.rs
More file actions
126 lines (123 loc) · 5.12 KB
/
Copy pathac3_decode.rs
File metadata and controls
126 lines (123 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Decode an AC-3 / E-AC-3 elementary stream to raw f32le PCM with the
//! in-tree decoder — the counterpart of `ffmpeg -i x.ac3 -f f32le`.
//!
//! cargo run -p rivet-codec --example ac3_decode -- in.ac3 out.f32 [drc_scale] [noise_seed|off]
//!
//! Prints the stream layout, the decoder's dither / dynrng statistics and the
//! coding tools the stream exercised. Leading junk before the first
//! syncframe is skipped and a truncated final frame dropped, as libavcodec
//! does. `RUST_LOG=trace` prints the decoder's per-frame / per-block syntax
//! trace on stderr; `AC3_DECODE_FRAMES=1` prints one line per syncframe with
//! the coding tools that frame used (the deltas of the `Features` counters),
//! for lining up against a per-frame error report.
use std::io::Write;
use codec::audio::decode::ac3::{FrameDecoder, frame_crc_ok, parse_header};
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
eprintln!("usage: ac3_decode <in.ac3|in.eac3> <out.f32le> [drc_scale=1.0] [noise_seed|off]");
std::process::exit(2);
}
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.with_writer(std::io::stderr)
.without_time()
.init();
let drc: f32 = args.get(3).map(|s| s.parse().expect("drc_scale")).unwrap_or(1.0);
let es = std::fs::read(&args[1]).expect("read input");
let mut dec = FrameDecoder::new(drc);
match args.get(4).map(String::as_str) {
Some("off") => dec.set_noise_fill(false),
Some(seed) => dec.set_noise_seed(seed.parse().expect("noise_seed")),
None => {}
}
let mut pcm = Vec::new();
let mut pos = 0usize;
let mut frames = 0usize;
let mut bad_crc = 0usize;
let mut skipped = 0usize;
let mut last = None;
let per_frame = std::env::var_os("AC3_DECODE_FRAMES").is_some();
let mut prev_feat = dec.features();
while pos + 8 <= es.len() {
let hdr = match parse_header(&es[pos..]) {
Ok(h) => h,
Err(e) => {
if frames > 0 {
eprintln!("frame {frames} at byte {pos}: {e}");
break;
}
skipped += 1;
pos += 1;
continue;
}
};
if pos + hdr.frame_len > es.len() {
eprintln!("frame {frames}: truncated ({} of {} bytes), dropped", es.len() - pos, hdr.frame_len);
break;
}
let frame = &es[pos..pos + hdr.frame_len];
if !frame_crc_ok(frame) {
bad_crc += 1;
}
match dec.decode(frame, &mut pcm) {
Ok(Some(h)) => last = Some(h),
Ok(None) => {}
Err(e) => eprintln!("frame {frames}: {e}"),
}
if per_frame {
let f = dec.features();
let d = |a: u64, b: u64| a - b;
eprintln!(
"frame {frames}: blksw {} cpl {} phsflg {} remat {} deltba {} dynrng {} spx {} spxatten {} aht {} gaq {} vq {} gaqbins {} large {} mixed-blksw {:?} dith-offsets {:?}",
d(f.blksw_chblocks, prev_feat.blksw_chblocks),
d(f.cpl_blocks, prev_feat.cpl_blocks),
d(f.phsflg_blocks, prev_feat.phsflg_blocks),
d(f.remat_blocks, prev_feat.remat_blocks),
d(f.deltba_blocks, prev_feat.deltba_blocks),
d(f.dynrng_blocks, prev_feat.dynrng_blocks),
d(f.spx_blocks, prev_feat.spx_blocks),
d(f.spx_atten_channels, prev_feat.spx_atten_channels),
d(f.aht_channels, prev_feat.aht_channels),
d(f.gaq_channels, prev_feat.gaq_channels),
d(f.vq_bins, prev_feat.vq_bins),
d(f.gaq_bins, prev_feat.gaq_bins),
d(f.gaq_large_mantissas, prev_feat.gaq_large_mantissas),
dec.mixed_transform_blocks().iter().filter(|&&b| b / 6 == frames as u64).map(|b| b % 6).collect::<Vec<_>>(),
dec.dithflag_bit_offsets(),
);
prev_feat = f;
}
frames += 1;
pos += hdr.frame_len;
}
let mut out = std::io::BufWriter::new(std::fs::File::create(&args[2]).expect("create output"));
for v in &pcm {
out.write_all(&v.to_le_bytes()).unwrap();
}
out.flush().unwrap();
let (dithered, total) = dec.dither_stats();
let (drc_blocks, blocks) = dec.drc_stats();
match last {
Some(h) => println!(
"{} frames ({} junk bytes skipped), {}{} acmod {} lfe {} → {} ch @ {} Hz, {} kbit/s; {} samples/ch; bad crc {}; dithered {}/{} bins; dynrng blocks {}/{}\nfeatures: {}",
frames,
skipped,
if h.eac3 { "E-AC-3" } else { "AC-3" },
if h.eac3 { format!(" ({} blk)", h.numblks) } else { String::new() },
h.acmod,
h.lfeon,
h.channels(),
h.sample_rate,
h.bitrate_kbps,
pcm.len() / h.channels().max(1),
bad_crc,
dithered,
total,
drc_blocks,
blocks,
dec.features()
),
None => println!("no decodable frames"),
}
}