本帖最后由 shane007 于 2023-9-2 00:06 编辑 8 M O& S: s2 z6 |" Q( C8 y% Q; Z
; G5 P% x: B0 S7 V( z) M; Q该游戏是scummvm支持的少数几款FMV AVG。% A+ f1 G( Y3 d: Q: E5 i
视频采用了一种叫做RL2的格式。% i' X8 l T G5 H8 m+ w) e- r$ a5 v
参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,8 L- z; x {# f$ } L7 _% l. z
然后用whisper语音识别之后就能配上字幕了。2 s/ t2 o9 \) E: j9 M
此外,rl2格式用potplayer也能直接播放。
0 H* G k9 \& Q% f# Y$ ?! Y; S$ I9 u! o$ r Q: f" r5 o
文件格式. Q4 t4 T# Z% m7 b
https://wiki.multimedia.cx/index.php/RL26 [! W# [6 U0 q' q
0 S3 o8 x0 |: N- + 0 dword Header -- "FORM"
9 d9 |7 I- Q3 l% D; X5 `& M; i3 h - + 4 dword BackSize -- size of the background frame
9 J1 b: d: m) o4 r' |' | - + 8 dword Signature -- "RLV2" or "RLV3"
8 P0 z( v" Q. ]. K+ g - + C dword DataSize -- size of the data past this point BIG-ENDIAN7 p9 Z G8 E( n% o3 j6 z: l
- + 10 dword NumFrames -- number of frames
3 D, A4 k$ N: E5 ]# `8 u/ [% { - + 14 word Method -- encoding method. ignored, zero
6 R; `$ F2 O* }# x - + 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound
& ?% F1 w. g4 y8 z' R& d% M8 } - + 18 word Rate -- sound sample rate in Hz
4 c4 y0 s" u( J; z' v. s6 e - + 1A word Channels -- number of sound channels
8 B R( H- { c7 d* y/ c, Z - + 1C word DefSoundSize -- size of the single sound chunk. see notes below0 i5 l# { U2 W5 V" t
- + 1E word VideoBase -- initial drawing offset within 320x200 viewport3 X0 g5 h0 b$ p1 O; L: X4 P+ p
- + 20 dword ClrCount -- number of used colors: F+ F, j# l- I3 B" r4 _% y
- + 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries! q g4 a- u5 K. o
- -- if Signature == "RLV3" AND BackSize <> 0
- \) w! q2 r& { - +324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding9 ^; I4 ^5 c1 ^2 J9 T
- --
& E0 D4 j" A& ^1 [( e% j; h - +xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video)
- H4 ]8 ]- L% M) K M1 D+ ]2 h - +yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file. w$ _- x9 P8 p, I
- +zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk
: X6 ]4 {8 O9 T! W1 f' u - -- for each frame --9 P: H' _. @* `: W6 a
- +xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio8 R0 K) |) O4 q% x; V7 p2 U1 N1 N
- +yyy byte Video[...] -- compressed video stream
复制代码 * L! x; [! N) z. C7 H
参考代码(有问题,但可参考)- using System;1 N$ u( O- P% D1 F+ [
- using System.IO;+ q6 ~* t2 |# f: z5 m8 G) [
- using System.Text;
) r" ]% X. r$ }8 c
' u2 N# x W) F* W2 D* V- public class RL2ToWavConverter
2 T+ f5 D5 `9 o5 g# ~' [8 |% J - {
2 T/ w3 O2 Z/ w1 o6 Q - public static void ConvertToWav(string inputFile, string outputFile)+ g% }& Z; n3 Z: \4 H4 |, [
- {
) I" q6 {( ]- j - using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read))/ G7 |2 c) a2 X; T
- using (BinaryReader reader = new BinaryReader(fs))) e0 `" l" Q* ^2 b n
- {
! k0 q+ l1 w$ {2 H - // 读取头部
* {9 E$ n3 U& B! y) h- b - string header = Encoding.ASCII.GetString(reader.ReadBytes(4)); {' [4 `& h& e
- if (header != "FORM")6 o4 L! Z2 x! S" _ u
- {
9 l) e- k2 T; N) z - Console.WriteLine("无效的.rl2文件格式。");
8 Y8 m3 ^/ \/ T$ }; w/ b* L: F - return;
+ a' J$ k6 { |# \- A# G - }+ b4 t6 S; L+ I2 P
- 7 o+ G( O* F* e
- uint backSize = reader.ReadUInt32();
# u/ q8 q2 F' ? - string signature = Encoding.ASCII.GetString(reader.ReadBytes(4));! q" o' a8 }# S6 x3 i" Z% R
- uint dataSize = reader.ReadUInt32();; _1 E' N9 l& o! n2 O& m
- uint numFrames = reader.ReadUInt32();
# {! G* S: D2 I, }4 K* K4 v) u4 [! X - ushort method = reader.ReadUInt16();
; U9 E3 _4 `' @4 p; }0 e - ushort soundRate = reader.ReadUInt16();$ n _: g& D* z& _/ F4 a
- ushort rate = reader.ReadUInt16();
3 Z2 [! r R% ?. b* }# v! ^8 y+ p. D - ushort channels = reader.ReadUInt16();
' W; |$ g8 V! p* h3 G - ushort defSoundSize = reader.ReadUInt16();
" W8 ^2 y) W6 S0 V - ushort videoBase = reader.ReadUInt16();
3 X% d2 ~7 N N* C - uint clrCount = reader.ReadUInt32();
: Q6 W0 e! }. A. m/ }/ A2 W - uint[] chunkSize = new uint[numFrames];4 C' j( B) C8 M
- uint[] chunkOffs = new uint[numFrames];1 U# C2 \4 L6 T0 G' o
- uint[] soundSize = new uint[numFrames];
6 y h: I& {" t5 v
* e5 ~: V& d1 m% [* o6 p5 f- if (signature != "RLV2" && signature != "RLV3") J( l& T3 _! x0 f C6 D: J) [
- {
7 Q; F3 c7 b& ^' d% k4 O% E1 ]2 B* r" L - Console.WriteLine("不支持的签名。");
5 y" e, L6 a4 G8 S - return;) B2 q( @8 e. N3 T4 m( }
- }
! M1 E1 S) V$ g9 f2 p
! \7 ^5 @% u0 T- W" c- // 读取块信息
7 z) j+ ?' h$ r3 I7 O - for (int i = 0; i < numFrames; i++)
! I2 U6 o4 I# r7 K; f1 b - {
2 P- v+ c$ y h' a A2 f - chunkSize[i] = reader.ReadUInt32();
$ u/ y3 m. m }: J. r2 Z - chunkOffs[i] = reader.ReadUInt32();
0 a8 u S- a9 P. l - soundSize[i] = reader.ReadUInt32();
* t6 l: w; L5 S - }' z. {1 Q( J, @) h% I7 R
- 7 L2 I; D, C2 z
- // 如果存在背景帧,请跳过它# n: Y' f4 [2 v9 c
- if (signature == "RLV3" && backSize != 0)
! a. H/ `' X# j) q% w& r3 F - {
9 {' I( F$ [# Z: U - reader.BaseStream.Seek(backSize, SeekOrigin.Current); z! K- ^) _* p$ A! \8 @" `% W" o
- }& G0 h: N! l& d! K" X
- 0 ?: `& x, L- G( b7 _5 a
- // 创建一个WAV文件并写入音频数据, D3 H$ u2 V/ b. i" Z
- using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create)))& n$ C: _9 Q* n% y' n1 x
- {# d% C. _' w( b ~, V
- // 写入WAV头部) g( V) l7 T1 y: u8 D; g( E2 I9 @# g
- wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));2 G8 W' c1 |) N- y: [' J) L
- wavWriter.Write(36 + dataSize); // 总文件大小 - 8) N; y* ]* ?' A/ V6 c5 Q1 C* ]1 M
- wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
. o0 ]) s- ]9 r J! U - wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));
; _! |" p; i' s( u ]) s! K' d - wavWriter.Write(16); // fmt块大小: _+ {8 ?. z' ^' K2 G# V; w
- wavWriter.Write((ushort)1); // 音频格式(PCM)/ K; C2 R9 b e
- wavWriter.Write(channels); // 声道数
0 r2 O! c! r$ F - wavWriter.Write(rate); // 采样率
6 ?! h# q1 ]( P - wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数. V+ o: J/ U% m9 [/ ]% U
- wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数+ M3 Q# r, P8 O2 ]
- wavWriter.Write(defSoundSize); // 每个样本的位深度* Y$ B5 T3 _; K1 C; L
- wavWriter.Write(Encoding.ASCII.GetBytes("data"));
& A: W* P& l! c1 h1 u5 ~$ Z# } - wavWriter.Write(dataSize); // 数据大小
7 M0 J% k' }& u% Z/ }8 l& W6 v+ E
: ?% m; J* g. ]7 J3 ?7 ^6 D- // 从.rl2文件中读取并写入PCM音频数据
* `7 _. v+ e4 [1 ~' X - for (int i = 0; i < numFrames; i++)5 H3 g2 g: z# u }3 |+ S( o
- {3 _ K* c1 f! G# X
- byte[] audioData = reader.ReadBytes((int)soundSize[i]);& j" w/ p) `" ^* N( [4 v
- wavWriter.Write(audioData);
% l+ X6 F! a$ Y. S$ T/ d& y5 ] j - }
# j. P" O7 j( B! m' u$ k - }
, o, F1 n! Y$ r3 [' v% F - }
# i9 }3 x1 M. e, ? - }
# a4 A- ]: }, n' Y5 e. j - }. C) j& ]4 O" A2 f5 |2 t5 F# u
4 \& p% ~+ O% H- H- class Program
5 l9 o+ r+ Z6 @6 [& [ - {
6 a' @3 S' l' [( n - static void Main(string[] args)
" F1 s9 z V: ~ - {
3 _" i: M6 I8 g5 F - string inputFile = "N1275510.RL2";/ }- U$ ~" X, F4 j$ g* v* ]
- string outputFile = "output.wav";, r& g+ N5 a1 r9 B* z
- RL2ToWavConverter.ConvertToWav(inputFile, outputFile);# n* ?! ]5 T; G' v$ H( ]
- Console.WriteLine("转换完成。");; I& m4 H- |# S8 E6 X; l
- }1 M- }4 b$ d2 B8 P! a
- }
8 v( [3 F; ?0 K; p: ^; K% q
复制代码
& _4 a2 T2 _8 \3 B9 P7 ~6 k
1 \0 l6 [ E" E1 Z/ S
1 r6 C- ?$ N c: A% B9 z6 O |