You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
65 lines
2.4 KiB
65 lines
2.4 KiB
import 'package:eaimar/data/utils/ogg_opus_duration.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
/// Ogg Opus 时长解析的金测试。
|
|
///
|
|
/// 用例是 **EaiRec 支架真机 TF 卡上那条录音的原始字节**(2026-09-10 拉下来的
|
|
/// `MIC0005.OGG`,38400 字节),不是构造出来的样本。改这里的解析逻辑前先看
|
|
/// 这个测试为什么红。
|
|
void main() {
|
|
// 文件头 48 字节:第一个 page(含 OpusHead)
|
|
const head = <int>[
|
|
0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, //
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x17,
|
|
0x30, 0x89, 0x00, 0x00, 0x00, 0x00, 0x21, 0x8B,
|
|
0x55, 0x8A, 0x01, 0x13, 0x4F, 0x70, 0x75, 0x73,
|
|
0x48, 0x65, 0x61, 0x64, 0x01, 0x01, 0x00, 0x00,
|
|
0x80, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F,
|
|
];
|
|
|
|
// 最后一个 page 的头 27 字节:granulepos = 0x0006B580 = 439680
|
|
const tail = <int>[
|
|
0x4F, 0x67, 0x67, 0x53, 0x00, 0x04, 0x80, 0xB5, //
|
|
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x17,
|
|
0x30, 0x89, 0x1E, 0x00, 0x00, 0x00, 0xF3, 0xEE,
|
|
0x70, 0xEC, 0x0A,
|
|
];
|
|
|
|
test('真机 MIC0005.OGG:granulepos 439680 → 9.16 秒', () {
|
|
final secs = oggOpusSecondsFrom(head, tail);
|
|
expect(secs, isNotNull);
|
|
// 439680 / 48000 = 9.16
|
|
expect(secs!, closeTo(9.16, 0.01));
|
|
});
|
|
|
|
test('⚠️ 除数必须是 48000 而不是音频的 16000', () {
|
|
// OpusHead 里写的输入采样率是 16000(head 偏移 40 起的 0x00003E80),
|
|
// 但 granulepos 恒以 48kHz 计。用 16000 去除会得到 27.48s —— 3 倍。
|
|
final secs = oggOpusSecondsFrom(head, tail)!;
|
|
expect(secs, lessThan(10));
|
|
});
|
|
|
|
test('pre-skip 要从 granulepos 里扣掉', () {
|
|
// 把 OpusHead 的 pre-skip 从 0 改成 48000(1 秒)
|
|
final h = List<int>.from(head);
|
|
h[38] = 0x80; // pre-skip 低字节
|
|
h[39] = 0xBB; // 0xBB80 = 48000
|
|
final secs = oggOpusSecondsFrom(h, tail)!;
|
|
expect(secs, closeTo(8.16, 0.01));
|
|
});
|
|
|
|
test('不是 Ogg 就返回 null,不猜', () {
|
|
// RIFF/WAV 头
|
|
const wav = <int>[0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00];
|
|
expect(oggOpusSecondsFrom(wav, tail), isNull);
|
|
});
|
|
|
|
test('granulepos 不大于 pre-skip 时返回 null(空文件/只有头)', () {
|
|
// 最后一个 page 的 granulepos 置 0
|
|
final t = List<int>.from(tail);
|
|
for (var i = 6; i < 14; i++) {
|
|
t[i] = 0;
|
|
}
|
|
expect(oggOpusSecondsFrom(head, t), isNull);
|
|
});
|
|
}
|
|
|