本帖最后由 shane007 于 2023-9-2 00:06 编辑
S+ U% r. q0 P, E* U
% x% n2 L! l5 z9 Y! v该游戏是scummvm支持的少数几款FMV AVG。
4 B+ H/ T5 ?+ [$ i8 K视频采用了一种叫做RL2的格式。 `0 N6 S z2 B8 `
参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,
( \- }3 [* h/ X+ l然后用whisper语音识别之后就能配上字幕了。
6 t ]. l0 x8 k0 b+ {( @* |0 Y% d此外,rl2格式用potplayer也能直接播放。) m2 Z" p* @0 p7 {+ b
6 f& Y q( |3 t) T. J' s
文件格式5 Y4 G( U1 o& _2 X
https://wiki.multimedia.cx/index.php/RL2' N; z/ s% O( L/ e$ _. l
# ~* O- }% Q. y0 v) u5 }- + 0 dword Header -- "FORM"
5 {! D; ^+ v6 k - + 4 dword BackSize -- size of the background frame
+ c/ Y8 m3 p X6 C- T - + 8 dword Signature -- "RLV2" or "RLV3"
% T9 B, R I( C% g" ^ |: S6 c; u* n - + C dword DataSize -- size of the data past this point BIG-ENDIAN$ ]. {! j6 J+ X: Z g8 P- U
- + 10 dword NumFrames -- number of frames! p3 Y( S/ d7 |+ m
- + 14 word Method -- encoding method. ignored, zero
, F0 i4 [/ L3 W. H: g - + 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound
4 T- M! O# z5 P s: K, G- p - + 18 word Rate -- sound sample rate in Hz
6 {5 G) J1 y/ y, d - + 1A word Channels -- number of sound channels
7 ^- ?3 \/ L+ `6 h/ x% y7 k - + 1C word DefSoundSize -- size of the single sound chunk. see notes below2 n, E% F' U8 z0 k9 R+ M' g8 J
- + 1E word VideoBase -- initial drawing offset within 320x200 viewport1 v4 q0 Z4 H; x. \! z
- + 20 dword ClrCount -- number of used colors6 R" L9 L e7 Y' ?: n
- + 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries
; H4 ^8 w$ Q, e* j+ Z - -- if Signature == "RLV3" AND BackSize <> 0
- Z b c; u$ z0 N - +324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding
! H$ L5 Z, V6 ]% J- r - --. W/ }9 d$ f1 q9 j/ V
- +xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video)# V# ?) }* y$ P, N7 y
- +yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file( W! \$ g1 \# [0 O p. r
- +zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk" x9 ` @3 R: g s0 h* o
- -- for each frame --6 x' R; Y4 a9 ~: {0 W A( {
- +xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio
) z) G& M) C# i4 _5 w8 x; }6 P - +yyy byte Video[...] -- compressed video stream
复制代码 0 p# }# t K; b3 z2 U% ^, {3 |
参考代码(有问题,但可参考)- using System;6 W$ k3 a2 E, p4 A) \( z; ^ W$ @" H V
- using System.IO;6 [2 \/ g1 `* g; }! w. f" R3 { u
- using System.Text;
# P! {0 H4 e# C% A' e
" X% i4 A, {" G0 S9 ?+ E- public class RL2ToWavConverter
, Q* h" y$ d, a& J - {
, b/ Z8 L! t% L - public static void ConvertToWav(string inputFile, string outputFile)
L" V: w5 c& Q! `5 _1 i: l - {7 }# R5 }& d/ j0 ]; z5 d
- using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read)): B) a; ~3 v9 G$ f- H
- using (BinaryReader reader = new BinaryReader(fs))
' J! v* ~2 u. b0 L: \ - {
9 C: ?5 O( a& m* b- L$ C - // 读取头部7 P/ C, o/ B% r% K
- string header = Encoding.ASCII.GetString(reader.ReadBytes(4));
7 r. c: ~5 T+ e8 n$ b. j/ G - if (header != "FORM")3 E* X: Q' U) b9 j5 {
- {
8 g: k- a( o- j6 T - Console.WriteLine("无效的.rl2文件格式。");
3 ^" H, K) f: k3 u5 f6 ^/ T - return;( m5 w+ A, I- X4 n( Z, y; f+ V/ [9 M
- } {/ \: s1 p7 x% E4 q y
. j2 s* Q5 o# ?) j; q- uint backSize = reader.ReadUInt32();; i( ~4 {& p( R. t% P. p2 c: Q+ F3 e
- string signature = Encoding.ASCII.GetString(reader.ReadBytes(4));& d5 b3 P2 [- t/ W% |) m6 [
- uint dataSize = reader.ReadUInt32();
3 \# i. `9 @* b; q - uint numFrames = reader.ReadUInt32();
4 E+ h6 n: ~8 A ^: i" U! \ - ushort method = reader.ReadUInt16();
9 x4 T0 A; g/ t% A8 j7 F- F1 [5 B7 Q - ushort soundRate = reader.ReadUInt16();1 C+ z/ T' p7 `3 C
- ushort rate = reader.ReadUInt16();
" n: ~/ ^7 {7 ~& o O - ushort channels = reader.ReadUInt16();
' [$ o! N0 a* o, g# K - ushort defSoundSize = reader.ReadUInt16();) j: Q. J) x' u- M, Z
- ushort videoBase = reader.ReadUInt16();# ~7 U& h6 o1 E& A0 W% U( |
- uint clrCount = reader.ReadUInt32();
4 t \) K& B8 S) J5 y$ d - uint[] chunkSize = new uint[numFrames];
0 R1 `* M0 ?3 o" w$ D# Y - uint[] chunkOffs = new uint[numFrames];* L* p2 G* R; @: H% S+ G, t: m' q
- uint[] soundSize = new uint[numFrames];; g- Y( }; _& Z
* |7 O8 K" O$ \+ o+ f- if (signature != "RLV2" && signature != "RLV3")
* C6 K% n8 }% [* C4 j4 ^ - {8 j- c1 M7 U1 ]) V, H# G0 X
- Console.WriteLine("不支持的签名。");
4 y) R1 f5 m; |0 v4 W4 W2 u$ u0 W - return;: l( L! [5 O3 O9 `* X
- }* D1 J e, R i
- 0 U# l6 j% v! ~
- // 读取块信息
6 o+ R& }0 J* Q3 n: \% m+ h1 r - for (int i = 0; i < numFrames; i++)" Z5 L- o8 {2 ?0 a' c f. o
- {
! @$ z1 r! L1 A# {0 p - chunkSize[i] = reader.ReadUInt32();+ f! o. `- a& h# W
- chunkOffs[i] = reader.ReadUInt32();
1 t$ m' C- D9 |! J& Q3 J - soundSize[i] = reader.ReadUInt32();
9 j$ u- @$ `% V3 o" ?. v - }
' I7 o3 C2 H: [4 _% L: X - 8 z( p8 j: g: N! N a
- // 如果存在背景帧,请跳过它5 }+ |+ j6 ~2 s9 H0 P! p) u
- if (signature == "RLV3" && backSize != 0)
1 m7 Y4 K0 ]& i* `: t1 P* w2 H3 A - {
/ D: N8 a$ b; s2 W9 D4 C - reader.BaseStream.Seek(backSize, SeekOrigin.Current);
' Z) q) m5 a! h+ l- t; A. W - }
. R; y1 T ]8 V - / e9 S9 ], O3 S) e/ s
- // 创建一个WAV文件并写入音频数据
: k- U0 l, Q" D; G' I8 F# l, _ - using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create)))3 R4 o6 O. u" j4 B
- {2 w8 }% G' p! ?6 l- b) f
- // 写入WAV头部) R% E- u1 C, g t
- wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));- F/ _3 Z0 p( X' Q" X( C
- wavWriter.Write(36 + dataSize); // 总文件大小 - 8
: e( m, [& O7 b. P1 B2 i. Q - wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
. l( i: E" G4 C0 e - wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));1 B1 |: W/ l! e7 [
- wavWriter.Write(16); // fmt块大小
3 j: K9 y. L, j. n4 V" y - wavWriter.Write((ushort)1); // 音频格式(PCM)$ d" S. `3 R( M% d
- wavWriter.Write(channels); // 声道数; o+ |) V- Y' `4 Q, \% ^7 [4 R5 \* p
- wavWriter.Write(rate); // 采样率+ b( ]5 P: O# ?& z& F6 g& |8 F
- wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数
4 X1 ^/ `" T: e2 d2 d9 k - wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数0 m2 ~0 ~% r- Q# s9 b
- wavWriter.Write(defSoundSize); // 每个样本的位深度2 J- k# m3 v+ Q" g/ f5 O* j) ]
- wavWriter.Write(Encoding.ASCII.GetBytes("data"));2 d6 t- _& d9 A# O
- wavWriter.Write(dataSize); // 数据大小5 q# M0 ~) `8 L" h
- % ]1 C) Y: I1 e1 r9 f2 e& @
- // 从.rl2文件中读取并写入PCM音频数据7 p* N) x1 e2 `) ~+ g# x
- for (int i = 0; i < numFrames; i++)! z: H1 [1 x- `2 I) @' |% e$ D
- {. o! b( o3 v% g6 p
- byte[] audioData = reader.ReadBytes((int)soundSize[i]); E$ u: C) _: a8 ~$ D
- wavWriter.Write(audioData);
5 v! `7 f. e( r0 P) p! c - }% k9 S6 Z4 I- y
- }
; T, L0 z/ c" \& I - }
, F9 X7 H! p8 \( o- b' c" h - }
4 A) `- [( v1 g4 e& a - }
8 @5 C3 F" t$ k7 C6 {4 ?- ? - ; P1 m3 Z, y/ i; s2 \' q4 @
- class Program; _3 C. R7 T& n' T8 C
- {
! `. Y" ?0 Q1 }: t# k5 c& h9 m - static void Main(string[] args). H1 _9 d" E/ _$ J4 N! w
- {
7 S% Z5 q a9 s( ` - string inputFile = "N1275510.RL2";
/ N: d5 q" ~. U4 q( W& [ - string outputFile = "output.wav";
6 Y, K M' K9 L" _/ \' ^3 v) A/ O! Q - RL2ToWavConverter.ConvertToWav(inputFile, outputFile);
9 x6 H1 j) V# @ - Console.WriteLine("转换完成。");! M: R1 X2 W* @/ B
- }8 e8 t, S# D1 h
- }
( M5 I! _1 o# ~9 U
复制代码
! Q; z' O# y8 J/ j/ S( F; x* w
$ }/ q5 T6 t9 ]/ e) s, y, l
) F( E& h+ K1 F6 W5 [ |