本帖最后由 shane007 于 2023-9-2 00:06 编辑
9 x, g2 y) O9 u: U( w5 S t8 t, F* s
6 k7 |2 M' ?- F- l7 Z该游戏是scummvm支持的少数几款FMV AVG。
1 e- f; ^2 s0 c$ A% M( @视频采用了一种叫做RL2的格式。8 L& w9 s4 }/ c! h8 j& e
参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,7 Q. |! D) r2 s2 M
然后用whisper语音识别之后就能配上字幕了。
+ x$ K, {+ Y4 ?此外,rl2格式用potplayer也能直接播放。
: } J2 x9 j) m) n' X5 q- } E. g8 Y4 O, G+ I) A2 g" [" ~$ y
文件格式
) V. s" n1 [; A5 y3 phttps://wiki.multimedia.cx/index.php/RL2/ a4 L6 w5 A* V# Z& @
- & V+ T! ^) e0 @
- + 0 dword Header -- "FORM"! B' \" n' V' Q6 u* F
- + 4 dword BackSize -- size of the background frame# P2 f$ u; Q; o1 w% G% F, [ a- @
- + 8 dword Signature -- "RLV2" or "RLV3"0 k0 q1 w/ s$ w* D
- + C dword DataSize -- size of the data past this point BIG-ENDIAN9 G/ q7 W1 S2 \# O( w. g' T# J( U( [4 N
- + 10 dword NumFrames -- number of frames
. e& x# J* n F L: G1 q) Z, _& l; w3 l - + 14 word Method -- encoding method. ignored, zero
& x* W6 C/ [8 J6 g6 H, r) M: V - + 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound
( w6 X. z0 T# P J - + 18 word Rate -- sound sample rate in Hz
9 j, S' G2 v0 W S0 R& F3 r - + 1A word Channels -- number of sound channels& q- `6 X/ s( a
- + 1C word DefSoundSize -- size of the single sound chunk. see notes below
% z2 P$ @: g. }& y - + 1E word VideoBase -- initial drawing offset within 320x200 viewport# }% B A8 x0 S8 i# G
- + 20 dword ClrCount -- number of used colors
6 a2 K6 E& Q5 \) _ - + 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries
0 R, u# X' ^& `# E3 I V+ g - -- if Signature == "RLV3" AND BackSize <> 0
5 C ^ V+ H- j! F! G - +324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding" e9 K9 ~' W' A3 `: V8 U
- --4 z& [' `" [ e# z& F' `
- +xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video)
?5 |5 S& K6 z - +yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file1 _6 X- g' S- a# w5 Q1 Q, H, c
- +zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk
0 B8 z# } c0 ~4 M, j - -- for each frame --5 s4 {! M( }* |1 a
- +xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio( V0 a+ G! j6 x
- +yyy byte Video[...] -- compressed video stream
复制代码
6 Z( o) D, n0 |) j) w参考代码(有问题,但可参考)- using System;8 x- b; S8 z e- ~( J, H% U
- using System.IO;+ [- R3 z: e& q. e' U2 Y( r
- using System.Text;
# u6 k. d' A$ X4 l- r8 a1 q
! q8 f/ T/ V s2 e% b# |- public class RL2ToWavConverter; Z7 G; p" O/ _$ z8 h o
- {
' C5 a; K3 O8 y8 V' d/ F% H' ^ - public static void ConvertToWav(string inputFile, string outputFile)
q' r4 e- s1 I8 z# [2 K" X - {
0 [) H0 ]/ d$ z( R: H* y/ J6 y - using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read)) L' t/ p" G$ R. w% N; U5 b
- using (BinaryReader reader = new BinaryReader(fs))
2 M( W6 ?3 E2 {" x3 \' }& Y - {
1 G7 f/ e: X+ w - // 读取头部+ U9 j8 Q9 |3 q$ @" e8 t
- string header = Encoding.ASCII.GetString(reader.ReadBytes(4));
) d4 G, r: I* J- U - if (header != "FORM")
w& J2 z9 A" z/ g1 z v. C - {9 K4 H) _# X% _$ y
- Console.WriteLine("无效的.rl2文件格式。");7 N% s8 ?, v! ^; @. {
- return;
, f6 N. d- p" E- M. Q7 ?& `* K - }
" T. w; A. \1 G - 1 h/ Q9 u `1 w1 `
- uint backSize = reader.ReadUInt32();% x5 B& m, @1 E% B- U3 a+ F
- string signature = Encoding.ASCII.GetString(reader.ReadBytes(4));. y. ?, v+ a4 m6 v4 O( E7 `; l
- uint dataSize = reader.ReadUInt32(); m; ~0 j$ j! o
- uint numFrames = reader.ReadUInt32();# N& o' F1 `* r# j- H! `; P. H
- ushort method = reader.ReadUInt16(); Y) a* E+ C8 t' U
- ushort soundRate = reader.ReadUInt16();
2 }7 H. {& ~0 Z2 {) L: s - ushort rate = reader.ReadUInt16();1 O' [0 ^9 G" v+ m( N3 h6 H2 h8 d- S
- ushort channels = reader.ReadUInt16();
- S5 A+ `" V. q7 Z+ X6 h3 H& ?9 E - ushort defSoundSize = reader.ReadUInt16();
1 `0 v3 i( n% [ ?( y - ushort videoBase = reader.ReadUInt16();
2 b# f$ | d4 ?9 Y) w9 | - uint clrCount = reader.ReadUInt32();
3 q6 n! P! I1 m7 s9 s$ h1 v2 |+ U - uint[] chunkSize = new uint[numFrames];. B* Y* i' X2 t
- uint[] chunkOffs = new uint[numFrames];
4 ] ?# S* _1 T* `1 `, e8 z - uint[] soundSize = new uint[numFrames];" _- M# O$ E) ]6 k8 a! R
* v, O4 I* B- H8 G/ }, V: {- if (signature != "RLV2" && signature != "RLV3")
1 z! U7 b) s; g, V3 N8 q7 j0 A+ [7 H - {8 _+ t' V+ p' h0 d. S9 {* v8 {" X
- Console.WriteLine("不支持的签名。");6 r; T7 \9 K+ i% L2 e3 j
- return;
7 k& v9 P+ s5 Q& `0 v" d1 } - }
Y2 v% y$ k; E4 @. q; m - ) n1 B) y; F# i, u/ X5 P5 v. F
- // 读取块信息
$ d) k8 K% _* k2 s# L" J$ J( O' u8 H0 q - for (int i = 0; i < numFrames; i++)
$ |; l, @8 Z/ U - {; v' X4 k" b( o" b
- chunkSize[i] = reader.ReadUInt32();
: m' Z+ e% j( ^% u. b$ d6 `" o3 ] - chunkOffs[i] = reader.ReadUInt32();6 W6 Q- |9 [: {# D: E- @7 Q R [
- soundSize[i] = reader.ReadUInt32();
; j2 J+ ^; ]' L - }* u7 b* g: Q" h: a+ D8 n
- 7 E2 _+ s; c# [+ q
- // 如果存在背景帧,请跳过它
9 y+ n. u3 j3 P) v - if (signature == "RLV3" && backSize != 0)
% Q g1 p( Q: X8 s" I6 j( _ - {, c; c2 e% ]" Q
- reader.BaseStream.Seek(backSize, SeekOrigin.Current);: }: [0 Z' |: V; T
- } m \& H% X0 {+ E! v
- - a' K7 i: t( y* H
- // 创建一个WAV文件并写入音频数据8 y! i0 K; p: K6 q# X! ?' D
- using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create))); ~8 I& ?4 [, p; A
- {
* s6 o. g$ E, B, x7 T% S - // 写入WAV头部, J( w& s% y0 \& }9 N( l
- wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));
- L# B! M# e, v& m7 D R! ^ - wavWriter.Write(36 + dataSize); // 总文件大小 - 88 A. c. [3 C2 O1 [, K4 I
- wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
0 L8 Z& q* V, E7 E+ r - wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));7 b* f& ], e$ a4 B% T" B/ U% C* M
- wavWriter.Write(16); // fmt块大小
! _& H k; b. T" Q - wavWriter.Write((ushort)1); // 音频格式(PCM). H. [5 Y- r$ W2 K5 C
- wavWriter.Write(channels); // 声道数* |" W" |+ B" J
- wavWriter.Write(rate); // 采样率 {8 o4 h- F3 _
- wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数
. Y1 J4 h s, ]- M - wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数# D3 a3 B9 _+ W+ H6 J# J9 E s
- wavWriter.Write(defSoundSize); // 每个样本的位深度3 T4 ], `( d/ z; P0 L
- wavWriter.Write(Encoding.ASCII.GetBytes("data"));
. h2 g5 j2 F, k/ D3 Z1 r# @ - wavWriter.Write(dataSize); // 数据大小
( T {0 Z& m: \! K, u4 W& g' M
1 Y/ B' }/ p. r% i! y- a0 {- // 从.rl2文件中读取并写入PCM音频数据- R& Z# l4 \8 i3 ]7 \& V2 i. m
- for (int i = 0; i < numFrames; i++)
) [' i; ^) m3 V$ x! l - {
7 D1 c& W. S/ |6 n3 ] - byte[] audioData = reader.ReadBytes((int)soundSize[i]);: A1 c g" w) S2 H4 B
- wavWriter.Write(audioData);
2 ^- w) N) K6 w! f" E g - }
0 k3 j9 R6 G" f0 a/ b- b6 u - }
4 G2 o4 X/ k! |, |& {. c - }
7 t D. ^# a; P, c - }( S8 m, R4 N1 C+ Y# S, A
- }
+ a6 J7 \4 u+ Q y# @
: H; d5 r% H6 x- class Program
- E1 _( X& g2 e/ k g; @& P' m - {
i/ S% M4 {# E, |5 F - static void Main(string[] args), |) t; i# o/ y8 f- q h
- {
% I( P; X( a" K& _3 G - string inputFile = "N1275510.RL2";7 I- m- x; N4 b5 T4 S& i2 C' ~" {
- string outputFile = "output.wav";
2 }& Y" r0 c* j5 T5 f! S/ B - RL2ToWavConverter.ConvertToWav(inputFile, outputFile);
4 `4 f, E: L: ]& W& L$ D - Console.WriteLine("转换完成。");; h4 ~! G- X; J& D
- }: Q' P( A u! A g% [2 p1 [
- }" w5 W; \+ D3 v3 m
复制代码
" N( I8 @) ]# x9 }- W- G
w/ ?7 j( a2 ]. f' }( S- J+ K5 ^5 |
5 |4 d* q+ p. f |