本帖最后由 shane007 于 2023-9-2 00:06 编辑
: m& t4 w9 j+ S2 C
. R8 v6 E# U0 J/ c Q该游戏是scummvm支持的少数几款FMV AVG。( `6 o- _' {0 U/ Q5 E
视频采用了一种叫做RL2的格式。
( ]8 f9 [2 M$ h2 `+ f3 c/ U: A j参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,
, z9 A z& a& K& i( i: U然后用whisper语音识别之后就能配上字幕了。" Y) x: I# L: y/ W; y; l
此外,rl2格式用potplayer也能直接播放。1 e. B2 i9 ^+ p0 W7 M0 n! A
7 q( p K T2 i K( M文件格式+ r% ~# R/ t8 P4 ^8 D# @" @4 j6 f0 N
https://wiki.multimedia.cx/index.php/RL2% L9 j* G |: P9 @- A& X. i( ^
- ; Q. p3 }; R# y; T
- + 0 dword Header -- "FORM"
9 b3 M- x4 l8 M4 |( Y - + 4 dword BackSize -- size of the background frame R) e8 _. A: V! t* P* q; U
- + 8 dword Signature -- "RLV2" or "RLV3"
* `' j4 @' q9 n. ?) W - + C dword DataSize -- size of the data past this point BIG-ENDIAN t# z; Z! u$ t$ `, L; T A- O
- + 10 dword NumFrames -- number of frames; u6 e/ E. N3 z) b
- + 14 word Method -- encoding method. ignored, zero: d/ u$ P: z0 b* O+ w( d* H4 h
- + 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound' X& T1 _5 r$ \& J
- + 18 word Rate -- sound sample rate in Hz W) A1 w8 J) h# e2 A* J" l
- + 1A word Channels -- number of sound channels2 m- e$ A" O; f' e
- + 1C word DefSoundSize -- size of the single sound chunk. see notes below! s) L! T6 L# H6 K# N5 V( U
- + 1E word VideoBase -- initial drawing offset within 320x200 viewport) W0 r" S7 u: R d( u( i: S' u
- + 20 dword ClrCount -- number of used colors* x. m# E) [& P$ d
- + 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries( W- r. d4 e2 P
- -- if Signature == "RLV3" AND BackSize <> 0
+ x" k' g+ U: s3 Y" B - +324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding
& j( `: l+ i. Q5 U. ?2 x - --
% F0 S- K) J. p7 Z9 P! K5 F - +xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video)
/ p* u4 |& x0 n; f0 v, v: p - +yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file
, K+ Q5 I6 h* y; A - +zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk
2 d6 d+ t2 g& A/ F - -- for each frame --0 @9 t* @ a1 U+ \9 H- m% m
- +xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio
9 k0 m+ K2 C f, d8 z' S - +yyy byte Video[...] -- compressed video stream
复制代码 & |: a' h4 i' {8 Q+ G- w. v
参考代码(有问题,但可参考)- using System;
2 h2 I1 H7 O5 A7 T: E- J - using System.IO;, T* X( \/ T# x( H" F
- using System.Text;
" A- O+ P3 }1 N2 `6 g) R - 2 B# w9 A/ z' _! J/ `$ @7 @
- public class RL2ToWavConverter
) @) f% e/ U2 i - {% A5 e4 Y3 U5 Y" O
- public static void ConvertToWav(string inputFile, string outputFile)
* Z: z; |1 t) K' k# _ - {. q3 v3 a2 ` k* S8 ], s `
- using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
' H5 o- n2 ~8 z& W, ]: K8 e o - using (BinaryReader reader = new BinaryReader(fs))7 R& w6 B/ J# b, s8 g6 s7 s5 X
- {
$ B% S* \2 C1 w2 b1 S8 E - // 读取头部
: r7 C/ p9 a) T0 T/ C - string header = Encoding.ASCII.GetString(reader.ReadBytes(4));
: V. }9 r' T* Z# q; b - if (header != "FORM")- D4 J6 _7 o( a5 f7 l- K
- {
4 m$ T9 I6 P% n - Console.WriteLine("无效的.rl2文件格式。");1 c4 q1 Q3 v/ I' |6 |! s2 B- y
- return;& i3 R. @ Z. @) w8 i
- }
9 m/ u8 Z1 j8 q+ n) X+ O9 } ^
: y1 l1 Z/ Q" q6 l' `5 m( ^0 h- uint backSize = reader.ReadUInt32();
) C1 r* ~, Z# U2 b! y+ Z - string signature = Encoding.ASCII.GetString(reader.ReadBytes(4));
( _* h& M8 j4 ? - uint dataSize = reader.ReadUInt32();7 I6 R- {9 @! a
- uint numFrames = reader.ReadUInt32();
$ V- h8 k$ Z+ c/ D0 w# g4 {8 [ - ushort method = reader.ReadUInt16();
# F+ L. H M, X- ^4 g - ushort soundRate = reader.ReadUInt16();
0 a0 I: e2 B W3 e+ o - ushort rate = reader.ReadUInt16();. |; Y: A, e2 a
- ushort channels = reader.ReadUInt16();5 |, i9 X6 h; v! I* {5 k4 B
- ushort defSoundSize = reader.ReadUInt16();
3 l9 p. L1 Z5 b7 r" e3 z+ b - ushort videoBase = reader.ReadUInt16();9 p v1 w& D* K9 \1 d
- uint clrCount = reader.ReadUInt32();
% [4 e: i5 ^* B - uint[] chunkSize = new uint[numFrames];
1 f8 F, U/ L8 W( \5 Y0 {6 R7 E6 d - uint[] chunkOffs = new uint[numFrames];
# R: b4 r& d7 E5 \" W! q9 W/ h - uint[] soundSize = new uint[numFrames];2 p$ v9 l5 \, z' {3 V: Z( v5 o
- " z- Z) }3 e; q$ p
- if (signature != "RLV2" && signature != "RLV3")7 N4 {& c3 ]2 m& Y, a ?
- {# g2 ]' a6 ?; w" a/ V
- Console.WriteLine("不支持的签名。");
: W% ^! A0 a2 w - return;5 b- k9 |/ M+ l+ x4 |9 b
- }: Q' t$ a& d- K9 C0 {7 L f5 T
- - S( R2 S" Y& F* H; V# V3 }0 [- }
- // 读取块信息) e' {/ l U' `% c: H/ |
- for (int i = 0; i < numFrames; i++)
2 G3 R4 t- N! K' h f1 [5 r5 J - {$ O# f' P6 D7 f( Q" G2 ~, [: T
- chunkSize[i] = reader.ReadUInt32();: I1 x- c* `% _$ B& U9 b
- chunkOffs[i] = reader.ReadUInt32();! d& B' v3 r1 c+ V5 w
- soundSize[i] = reader.ReadUInt32();2 X# Z; M# m1 G
- }: M$ a4 o+ q: C- ^, `
- 0 [4 s( j. y* E5 c B& r
- // 如果存在背景帧,请跳过它
, |1 M9 ^4 s, F$ d$ ~ - if (signature == "RLV3" && backSize != 0)
. Y% b3 [& Y1 w& A8 @- Y3 B( K - {: [+ e, u3 O$ x% J
- reader.BaseStream.Seek(backSize, SeekOrigin.Current);* E9 Q8 x! M" W2 V9 ?( M
- }# {# N! J4 Z6 M" d3 ?, z
- 3 e) L* s. D: P4 t* r7 _
- // 创建一个WAV文件并写入音频数据6 d* X2 L+ L$ b B" m- N
- using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create)))! e9 X, Z: k- Z& C6 \
- {
- j4 X I5 K' C/ i$ s8 F: i - // 写入WAV头部
. g, Y' ~7 ~3 C( X - wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));
: R& N; W, K( c0 v - wavWriter.Write(36 + dataSize); // 总文件大小 - 8
5 @0 ^# h) F) d - wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
) W8 u# B7 U" ?, R1 ^$ A - wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));
7 P0 F7 ~- q9 J z- E# V - wavWriter.Write(16); // fmt块大小& b9 a' Q' @3 ]4 @
- wavWriter.Write((ushort)1); // 音频格式(PCM)# d" o$ u, t. X7 ]- s% A, {3 E- E( n
- wavWriter.Write(channels); // 声道数
+ @1 j+ q. i9 |8 b! u8 t - wavWriter.Write(rate); // 采样率# U8 l9 J, o0 d% H" N/ ~
- wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数5 H+ d6 C( m4 V
- wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数
, g3 O$ b2 P! U, t- R. m% o: s1 r - wavWriter.Write(defSoundSize); // 每个样本的位深度& y- N) O: T0 @3 u Y. i4 K* I
- wavWriter.Write(Encoding.ASCII.GetBytes("data"));4 g& t Z7 P- k5 F$ ~6 A
- wavWriter.Write(dataSize); // 数据大小 z+ @6 P7 F5 J6 o
' D# t) Y1 Y5 n: e9 e- // 从.rl2文件中读取并写入PCM音频数据
' y7 w+ {! A8 k9 s7 v) b3 B* x - for (int i = 0; i < numFrames; i++)
! K/ O7 k+ K, X# F. R$ U# }; M - {
- D! ^( y, [/ S% L/ R1 k6 F, \ - byte[] audioData = reader.ReadBytes((int)soundSize[i]);
5 j% [4 N; o: D _ - wavWriter.Write(audioData);
: @5 ], L: Q L7 Q - }" G) ~& h, B4 @8 ?0 Q* D
- }
8 i; u W0 u9 u9 `& m2 u - }
% d) ?. l# s2 K - }7 N6 } z3 i% g% ~2 w2 p1 i* `
- }
6 \" G1 i( _, O, t
^( W7 w: V2 m1 V9 j- class Program7 F- Z8 ^3 M/ w* ]1 p
- {7 Y0 `8 m9 _- b0 O2 W
- static void Main(string[] args)1 ?* f* f) o& M8 g* `! C% t/ q" H
- {
+ s: {( ?0 R# \ S3 d! ?) j7 ~" c - string inputFile = "N1275510.RL2";
0 b: |9 F% Z* i9 o: Y - string outputFile = "output.wav";
' P0 W0 F# K+ z' A1 `, c3 N0 u$ G - RL2ToWavConverter.ConvertToWav(inputFile, outputFile);! T" K9 p7 ^; X; I
- Console.WriteLine("转换完成。");
, |$ w! U9 t( I& O. u- ^0 ]0 [: Z: r - }
# e3 F0 c9 s. | - }2 ]3 A. j6 g n8 @+ K$ Y$ P2 _, l
复制代码 4 J! n) F7 U, i: w1 \1 y) U
0 J, @: X# }) \6 g
3 y0 c6 ]% B! F& L8 u! R3 k! C% q( J |