本帖最后由 shane007 于 2023-9-2 00:06 编辑
& ?8 k4 R' [, C
2 b+ F$ G. N# ^) _, e该游戏是scummvm支持的少数几款FMV AVG。
; q/ D' W, J/ o, [, d视频采用了一种叫做RL2的格式。' m* y3 _2 e; a1 A$ |0 l4 s+ _& p, j
参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,
@5 F' a7 O8 h% u0 R然后用whisper语音识别之后就能配上字幕了。
; U: j8 @* {* k此外,rl2格式用potplayer也能直接播放。
: L5 `; @; j3 c: c% E3 {5 ~% G
# ~/ H. |/ z: Q8 n$ g, v( G, C4 R! F文件格式
5 n8 f& H3 b/ c! Q* R0 t: B5 k* ghttps://wiki.multimedia.cx/index.php/RL2
" p( l9 @# }' [6 N8 o- 2 L$ a5 j4 E5 {) ?* @8 N
- + 0 dword Header -- "FORM"2 g) N% O* p2 V* Q
- + 4 dword BackSize -- size of the background frame
1 A; a1 ^1 o: M k6 s - + 8 dword Signature -- "RLV2" or "RLV3"0 ~. _7 r: e+ R
- + C dword DataSize -- size of the data past this point BIG-ENDIAN
& _5 z5 ^6 ]8 o - + 10 dword NumFrames -- number of frames
9 {- \5 n! g0 v( B D' @$ b: ` - + 14 word Method -- encoding method. ignored, zero/ Y* m+ K8 t3 A9 u, i& n
- + 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound
3 Z1 [ F1 n7 o( \9 _: n0 N - + 18 word Rate -- sound sample rate in Hz
. J& }: W8 N' z C3 ~& D2 n+ r - + 1A word Channels -- number of sound channels
3 l5 y- u3 r: `& i9 y+ E( M3 | - + 1C word DefSoundSize -- size of the single sound chunk. see notes below+ u% u$ ]4 q0 i9 B* `- w, R
- + 1E word VideoBase -- initial drawing offset within 320x200 viewport
) N; D, ] D/ n) e( Z. h - + 20 dword ClrCount -- number of used colors
' q# H! g2 i) Q- @1 S! i" L - + 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries3 X+ K b z9 R! `( A1 Z% g
- -- if Signature == "RLV3" AND BackSize <> 0% t. z7 y' b+ Y( X$ O
- +324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding
! g1 h9 w' t9 |; A) r1 L - --
, T5 X* d0 i5 F6 z# u. B - +xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video)/ P) o R, F" X$ | t& S: b. V
- +yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file- c% y9 D6 Y4 s; j! Q1 G, M
- +zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk' v% N$ D' N+ Z7 u, O) Y
- -- for each frame --
! u4 q+ l3 b& G3 D - +xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio
+ N% G% {) H, C3 r3 { - +yyy byte Video[...] -- compressed video stream
复制代码 4 ]3 _& k$ W: g# ]& r
参考代码(有问题,但可参考)- using System;) G9 Z) _* c7 U% e5 y8 L& ]6 X
- using System.IO;
) n& V& _0 \2 _1 Q# a- K$ W. J - using System.Text;
3 N2 F2 R; V \; u) `% d- C - 6 j* a7 i, \6 d% J* z
- public class RL2ToWavConverter
1 P9 `, d6 x/ E - {
8 ]6 ]; m% @) s+ G - public static void ConvertToWav(string inputFile, string outputFile)
: y4 |2 Q5 |! h5 b( Y4 | - {
3 Y i& K) O( K/ c) H' x - using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
9 I$ R8 k5 d0 Y+ ]/ x6 t8 Q; |" @7 V - using (BinaryReader reader = new BinaryReader(fs))* O4 t. M m* v6 s* F
- {
4 j4 r: `8 P& _8 _! d: f0 u - // 读取头部
- q" G* D/ x* v5 `! p9 ` - string header = Encoding.ASCII.GetString(reader.ReadBytes(4));0 e- V4 S! R* C3 h3 k1 w: v
- if (header != "FORM")" [4 t7 d5 N: n9 F- s4 T2 @
- {
/ M2 ]* Q3 y, k1 B. S8 ^' O* E. f - Console.WriteLine("无效的.rl2文件格式。");9 ^& R: r5 X0 O/ D* y
- return;
( q2 k% v) R' z9 ^: q3 W* C: t - }: G( b0 O; U* Q+ j
- % W" [: j1 J* v$ S8 V, K
- uint backSize = reader.ReadUInt32();
0 h( \! o" c; @6 O& @/ i) q - string signature = Encoding.ASCII.GetString(reader.ReadBytes(4));
* I: {. P6 r( T O - uint dataSize = reader.ReadUInt32();
0 b$ ]& t" @0 p( {+ a. F" }) T - uint numFrames = reader.ReadUInt32();" s( j8 m( h+ F
- ushort method = reader.ReadUInt16();
~3 H. F" Q3 x' Q - ushort soundRate = reader.ReadUInt16();
4 C" E5 S9 Z1 C0 `$ V0 C - ushort rate = reader.ReadUInt16();% m: S' N/ V1 k8 y9 a& u3 A
- ushort channels = reader.ReadUInt16();
+ h" h, y9 J( x$ O - ushort defSoundSize = reader.ReadUInt16();
$ e, q7 T- n7 b' k4 e - ushort videoBase = reader.ReadUInt16();
& h2 }) j" U$ j T) k1 W' | - uint clrCount = reader.ReadUInt32();
- R7 q x7 P" }# Y, ` - uint[] chunkSize = new uint[numFrames];
7 U/ N* s! H) F/ ]. p, S. |1 c - uint[] chunkOffs = new uint[numFrames];, O& Z, t! f3 V6 Q9 O
- uint[] soundSize = new uint[numFrames];" l5 K1 t! \$ ^. B
1 ]1 t2 S; r6 |2 V0 j5 ~/ |( J- if (signature != "RLV2" && signature != "RLV3")
/ o: Y8 B( y" S/ |" V - {* s8 j0 ?$ @/ w" k6 z# J
- Console.WriteLine("不支持的签名。");; a7 [9 h9 Z' i; Y. A2 r
- return;* k" I3 `2 m) ^# D$ A! T, j: R+ w
- }
$ _* h; ]3 b1 h- |7 ]! E0 e - 8 w5 L7 t! B. y- e+ Q7 A
- // 读取块信息
) z' [9 M& R; o+ h; ]. P( ^. \1 q - for (int i = 0; i < numFrames; i++)5 ?" S& ?) X. N7 A) X
- {0 W! t' T! |& J/ q" E& d
- chunkSize[i] = reader.ReadUInt32();8 U; r! s' W+ x
- chunkOffs[i] = reader.ReadUInt32();
: W& z( d$ d/ C: Z) ? ` - soundSize[i] = reader.ReadUInt32();0 @8 H4 \) p$ w) F6 {
- }+ v/ D, z! k/ G' @
- 4 j1 J: U- {& y/ c9 n( C
- // 如果存在背景帧,请跳过它
/ S2 z3 `, ~6 i - if (signature == "RLV3" && backSize != 0)5 a- {1 ^* n2 M1 P% A: t6 w9 g) \+ j
- {% A1 j3 W9 J, T
- reader.BaseStream.Seek(backSize, SeekOrigin.Current);
& c6 F% F0 b; _. X( l0 J - } k+ V2 u# Q# P1 d/ R/ _3 P
: P, ~& ] O X% a$ G% d- // 创建一个WAV文件并写入音频数据
3 ~: R1 X* [7 G+ Q3 {8 c8 ] - using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create))) ]: P) h" r _3 o7 J
- {
$ w. w% Z1 b2 x4 U1 n - // 写入WAV头部
; \( t! c' ?. @ h0 [" V - wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));4 E) K! {; ?4 S$ B5 Y0 [ k; R! D
- wavWriter.Write(36 + dataSize); // 总文件大小 - 8: L, Y+ j7 `8 G0 n3 w( ^4 D
- wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));- Q) j/ N7 ?4 ^! c' D4 ~
- wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));/ @* }/ t. j& t. C9 t
- wavWriter.Write(16); // fmt块大小
, V/ k% d) O f/ ] - wavWriter.Write((ushort)1); // 音频格式(PCM)
' p% B# Z6 _9 J$ w2 ]6 Q. Y - wavWriter.Write(channels); // 声道数* s/ p+ q$ r8 n: ^7 }& j6 {9 s
- wavWriter.Write(rate); // 采样率* q7 T" S) N# d- A. q
- wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数4 ^! H N1 a6 h! C, Z$ `
- wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数
$ Z: [/ R7 T: n+ p. u - wavWriter.Write(defSoundSize); // 每个样本的位深度4 f5 l% Y4 V$ z1 D) O, c0 h
- wavWriter.Write(Encoding.ASCII.GetBytes("data")); m( V' R/ |- g
- wavWriter.Write(dataSize); // 数据大小! e- [& |2 q5 U! t; k# }
; I" ]0 o9 {4 c* L6 C- // 从.rl2文件中读取并写入PCM音频数据. {7 y5 q2 u$ k2 c! {
- for (int i = 0; i < numFrames; i++)! \, N. n$ u) `" W
- {1 W9 p# q; g' t0 O8 e
- byte[] audioData = reader.ReadBytes((int)soundSize[i]);
. X4 Z6 h; w5 g* Y) N/ `& J& m - wavWriter.Write(audioData);
$ X1 A" b* V1 P. `+ e; F Q! q - } g1 s. [7 j! g
- }
9 J$ S% C& s3 q7 i2 T6 m; ?' `/ ? - }
7 x Z0 s! @: R9 S" P - }/ e- \9 R- S/ a) o& [
- }
7 h- I( j+ t" _5 p) U5 h - P. k5 O7 f( \, I- o
- class Program
1 l, K; _( k; p0 j4 [3 J! k - {
. v) P- a) r3 E, |. w6 p& B: B - static void Main(string[] args)
* D. m1 h) ^% E - {6 [5 B! u% Y0 X) ?# k4 f: W
- string inputFile = "N1275510.RL2";) q' C. G' {5 a* t. Q. k3 Z% A, I3 R
- string outputFile = "output.wav";- }: b% Y% W5 m; Q
- RL2ToWavConverter.ConvertToWav(inputFile, outputFile);( y# @* S; Q! c7 N
- Console.WriteLine("转换完成。");
0 x+ L1 y/ D, r5 m k3 ? - }# u& J* `5 A1 z; p2 Z
- }# ^6 A2 X; {& f
复制代码 " R7 Q+ a& b" r$ u
* |+ j% L% b# F7 v9 a! j, s1 A8 g$ g1 U! ~# R8 x
|