本帖最后由 shane007 于 2023-9-2 00:06 编辑
, o5 Y% ~$ K5 q) p7 J, Y0 H) K" O* l
该游戏是scummvm支持的少数几款FMV AVG。
: ?( y; ]$ d& N. |视频采用了一种叫做RL2的格式。, e# j" Q# y6 A1 Y Y' k
参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,
. ^) X6 b" V# }+ A+ l! k然后用whisper语音识别之后就能配上字幕了。
) J( Y. U4 b1 o, m3 `此外,rl2格式用potplayer也能直接播放。
E! L; H' p1 Z5 ~( h- _! D' z! ~' Z
文件格式
9 i- z% n2 ]# n; k c( Ghttps://wiki.multimedia.cx/index.php/RL2
: g: v h* O; H) h5 u- - V; [' m0 ~; V: @6 S; X
- + 0 dword Header -- "FORM"! ]5 w2 `: G# |# Q3 i( A
- + 4 dword BackSize -- size of the background frame! {, M% O8 k# b$ j# a+ A7 x
- + 8 dword Signature -- "RLV2" or "RLV3"
& V8 @# `1 J9 [9 @0 m - + C dword DataSize -- size of the data past this point BIG-ENDIAN- ?( m0 e* f1 v, T/ _: _9 _5 d
- + 10 dword NumFrames -- number of frames: C+ T) o' v B
- + 14 word Method -- encoding method. ignored, zero
" o6 Q" _& R' `5 k6 K+ Q; N - + 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound, w" M% {8 Y3 E4 }% b: x
- + 18 word Rate -- sound sample rate in Hz
. E+ S: Q, e8 L) D9 M K( n' v - + 1A word Channels -- number of sound channels
( B8 C' `$ q$ ? L - + 1C word DefSoundSize -- size of the single sound chunk. see notes below
. I' D. H, b% `5 f5 F/ W4 k3 ~ - + 1E word VideoBase -- initial drawing offset within 320x200 viewport; K1 }+ @5 x+ N' P. v9 ]# G
- + 20 dword ClrCount -- number of used colors
6 n, h9 x4 }' M - + 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries
2 w4 v; n. h. P - -- if Signature == "RLV3" AND BackSize <> 00 b a/ x* y* _' n% [) {
- +324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding
& w7 [- j7 @; s/ Y3 y - --
/ B9 L" q. f( s" } - +xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video); W) Z3 Y8 @: t
- +yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file
, f$ D) A! E5 p7 s* ] - +zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk
/ u- v" x; n. Z4 b9 ? v( D! U4 X - -- for each frame --
! E" |, D8 J1 u' y7 ?5 Z - +xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio
6 _: o, s, J! b, J+ s4 M# s. t - +yyy byte Video[...] -- compressed video stream
复制代码 ; @( P: V W6 c/ |4 l
参考代码(有问题,但可参考)- using System;& g# b P! x t8 N( ^/ r
- using System.IO;
1 l! w9 @8 c* s4 h9 Q - using System.Text;& v4 M0 p8 H& }; T4 r2 y
- / J$ [/ ]. z! e" p' r7 ]2 T2 a
- public class RL2ToWavConverter. n: g& V2 _6 U
- {; I6 Y0 H4 x" [( {7 J
- public static void ConvertToWav(string inputFile, string outputFile)
9 h8 F4 Z Z0 @/ @ - {
" b7 U4 g$ [" u& ]# i2 c. B/ a - using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
1 L4 H% Z* t. g7 A0 J A - using (BinaryReader reader = new BinaryReader(fs))7 l+ R0 i2 {( c) W7 r% Q9 Z
- {
- Y! n: |* _8 h1 ] - // 读取头部
8 Q0 w7 u. q$ S9 c - string header = Encoding.ASCII.GetString(reader.ReadBytes(4));
}! U y3 c4 j- m; {: w0 @ - if (header != "FORM")$ N5 c) P/ W2 [$ x/ m ^4 y
- {
% A9 H9 G) t( r5 Q2 q. s3 L - Console.WriteLine("无效的.rl2文件格式。");7 c) _. Q; m0 c0 p9 `
- return;
. }& @7 \# `* k1 S - }: S/ U/ ^- H' H5 `
) U! K: x/ | a! e, e- L/ E: z- uint backSize = reader.ReadUInt32();
& L4 U: I7 \4 [. o4 R( f - string signature = Encoding.ASCII.GetString(reader.ReadBytes(4));
+ s0 v8 b' a x0 u% o - uint dataSize = reader.ReadUInt32();
`+ b( y5 d% b* h% O S& T5 V- N - uint numFrames = reader.ReadUInt32();& i/ @* L" A: m- K5 |# R. l* H
- ushort method = reader.ReadUInt16();( M/ Z' a8 d; K% t O9 |3 E
- ushort soundRate = reader.ReadUInt16();/ N- t1 i( @2 a( C1 e
- ushort rate = reader.ReadUInt16();( z% }6 I, K& x& }- Q
- ushort channels = reader.ReadUInt16();
% V% b4 `4 X, X+ r/ o - ushort defSoundSize = reader.ReadUInt16();! c# w, m7 y+ {+ u
- ushort videoBase = reader.ReadUInt16();
1 N G( k3 Z( f0 e1 ? - uint clrCount = reader.ReadUInt32();) h. D% ^! u2 w8 _% S. |8 y
- uint[] chunkSize = new uint[numFrames];1 _7 M/ x& H& o: V5 V+ W) {
- uint[] chunkOffs = new uint[numFrames];, A. b0 M) ]9 H9 r* I2 } G
- uint[] soundSize = new uint[numFrames];
! I5 q& A4 \% D# q7 b2 M - 3 g7 l' W4 ?# F3 \4 x' U2 ^, V0 U
- if (signature != "RLV2" && signature != "RLV3")9 u$ j/ z4 a) t# E" e
- {) a' V, I; d( X% }7 j. m: k1 [
- Console.WriteLine("不支持的签名。");
; a0 ]2 D5 E, d+ z+ r3 y - return;
% y) P2 {' m9 {* T' {; x - }
" c. {$ g# _/ |
% Q1 f& \$ k( f3 G3 v8 D- // 读取块信息
. R) R# v0 v1 H' M - for (int i = 0; i < numFrames; i++)( H) K! u( O3 i( E
- {
1 c+ C( s4 l& ?) H - chunkSize[i] = reader.ReadUInt32();& N! }8 V9 L6 K/ M8 [7 Z5 V! ]
- chunkOffs[i] = reader.ReadUInt32();8 ]- P* Q# y3 `& V& v6 n
- soundSize[i] = reader.ReadUInt32();
5 r7 `+ G. b! Q - }
# D; B& _+ z+ G - 2 S9 J9 m8 `5 C% z* T* u" e
- // 如果存在背景帧,请跳过它
8 L' V6 d0 h! K; U1 E/ U+ `; K8 e - if (signature == "RLV3" && backSize != 0)% @" O0 S' _3 q, g- f
- {
5 _# q1 o1 l/ C$ F# t - reader.BaseStream.Seek(backSize, SeekOrigin.Current);+ [5 h1 D# q( R9 Q! K1 V. s9 k
- }
0 q. A/ i6 O/ x4 h - , p% k: b6 _- ?7 R( `
- // 创建一个WAV文件并写入音频数据
7 g+ Q& e1 `. B - using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create)))
# u8 r' L6 e2 A8 v+ Z - {
: b- m# S# o6 P - // 写入WAV头部
7 a) B1 j' a( k; [ - wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));+ i% ]( R- |1 ~# V* b: a3 m$ w
- wavWriter.Write(36 + dataSize); // 总文件大小 - 8
P' U& I* G- A- ^% m! h - wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));+ t. ~8 b1 @% v- C) ]# k. k
- wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));
& ~$ `# V1 a0 z/ ^5 K. i - wavWriter.Write(16); // fmt块大小/ u% S8 m2 T9 o+ a9 o2 M0 a
- wavWriter.Write((ushort)1); // 音频格式(PCM)
1 [: \( Z4 C' P3 c$ h0 h - wavWriter.Write(channels); // 声道数$ j8 |- f0 j( a+ V3 P9 Q \' m+ {
- wavWriter.Write(rate); // 采样率
) D. V8 Y4 w0 t8 Y& v% T - wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数2 q1 e5 W) T( V* T) }) q
- wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数
0 V0 u6 Z" g- J1 i5 G- A - wavWriter.Write(defSoundSize); // 每个样本的位深度
. o0 e6 e: C; v7 m% Z8 t- Q - wavWriter.Write(Encoding.ASCII.GetBytes("data"));* `2 q" t, k8 @7 k& i, |
- wavWriter.Write(dataSize); // 数据大小3 Z1 @* e/ G8 I
, P7 N2 ^. ?- S+ {0 ~" w( I8 W- // 从.rl2文件中读取并写入PCM音频数据- G% E$ G8 z9 T( Z1 Q) k. ]& j0 E
- for (int i = 0; i < numFrames; i++)
- y, u4 i* z" P$ T6 i7 r: P6 c - {
& b; O9 H) c. p6 C - byte[] audioData = reader.ReadBytes((int)soundSize[i]);
; k* k6 J' k9 r n0 D - wavWriter.Write(audioData);3 v Q9 `" B5 Q1 [6 ~( C# T
- }+ b- V" g& U7 I% t; {/ b2 e% x
- }
% g) A# P! ]( Y8 }3 Y b9 s' s - }
- m3 `: m ^+ m8 g2 f - }* J" m( s. R/ j
- }
# l1 P& R+ r* U* @8 I& P; ^, {- e - 0 G+ m8 K( Z# W
- class Program, P2 B2 h' e3 C$ Y* g* P
- {6 @9 K9 x X( v/ T
- static void Main(string[] args)2 w- i" E' \0 n5 }
- {
& _: X9 o* x& s6 w" \ - string inputFile = "N1275510.RL2";
! |2 }1 E8 T. O, N, f& R - string outputFile = "output.wav";- |1 z3 b) c; f3 F
- RL2ToWavConverter.ConvertToWav(inputFile, outputFile);! [/ N* {; s% m% {% N
- Console.WriteLine("转换完成。");
2 f( l1 j( O$ U f! K - }
! X- s8 u% u# b" U4 y' C5 p" z1 l `& ^ - }4 d6 f1 }9 \; \5 j& e" V
复制代码 / N Z2 O0 I1 x( |5 P7 |' P
7 x) P8 \* I; n
. U( J1 `6 n0 }& F0 u/ w* T |