本帖最后由 shane007 于 2023-9-2 00:06 编辑 9 B: v. @ O$ ?' D% ]9 P, V
! I+ B; Q# V7 b" ]- [1 h
该游戏是scummvm支持的少数几款FMV AVG。
; Y* m+ `* ~5 H! O0 {4 y$ e# U视频采用了一种叫做RL2的格式。
8 B& Q* }' g0 H0 k+ d# L0 p8 C参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,
: r; \8 X' ]/ V, C然后用whisper语音识别之后就能配上字幕了。0 E' X1 Z( a% b/ C. q" o
此外,rl2格式用potplayer也能直接播放。
1 w* ?+ j) e' q. j1 Q/ ]6 d& H( ]9 I3 Y
文件格式
( A$ a4 d( A2 S4 ~7 @- xhttps://wiki.multimedia.cx/index.php/RL2$ q- G) @( c9 l/ W) U0 ]
1 N9 N I) \5 L A7 ~+ G$ n- + 0 dword Header -- "FORM") Y% X, R2 R+ _( e, R4 A* ]0 K
- + 4 dword BackSize -- size of the background frame8 K& H+ m. m4 u* S( [9 w
- + 8 dword Signature -- "RLV2" or "RLV3"
9 U6 q- p" I* m( L - + C dword DataSize -- size of the data past this point BIG-ENDIAN% I( x" o$ b5 k9 l j
- + 10 dword NumFrames -- number of frames4 f" Q, w& w& A" J
- + 14 word Method -- encoding method. ignored, zero
* m: ]! `( T) ] g: e; {3 N - + 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound
4 |6 O# `7 t8 e0 A$ S* O! ` - + 18 word Rate -- sound sample rate in Hz2 ] W2 H8 B# \5 s* D0 n' E( c
- + 1A word Channels -- number of sound channels- b% @' R0 x p: C/ B
- + 1C word DefSoundSize -- size of the single sound chunk. see notes below9 A! d2 h8 `5 }3 S! X2 ?
- + 1E word VideoBase -- initial drawing offset within 320x200 viewport
& n2 k3 t, h! i0 e. ^. u7 U; n - + 20 dword ClrCount -- number of used colors
: \7 _/ ]% A+ D: g - + 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries
8 c9 f# h( _: u, W" ^ - -- if Signature == "RLV3" AND BackSize <> 0
: B" q. u w2 f. o" c - +324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding
4 j, t4 v1 O4 ~5 { - --: R4 _8 G2 s4 y1 _! W2 p
- +xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video)
( d0 i1 A5 Z+ J: w+ k; H- W - +yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file
9 ?% v6 n& u- a9 {0 F8 E6 J1 M - +zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk
6 ?; Y6 p* P( V0 e' f - -- for each frame --
& X I" v# d+ q( {( w - +xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio+ L- u a+ X3 j+ T
- +yyy byte Video[...] -- compressed video stream
复制代码
6 _! ~( D1 q( F* V参考代码(有问题,但可参考)- using System;
! b% }" N E( @8 r0 e' t6 x8 z - using System.IO; J; l& _/ d! f# R8 z
- using System.Text;
6 f2 `% z5 Z- F
- `2 z. b8 y. Z" u2 f; q: C- public class RL2ToWavConverter: `' R/ T. y- I- A8 ]
- {
* u- T& C% A" i8 \4 s- e8 p - public static void ConvertToWav(string inputFile, string outputFile)
) }# N% S+ B# C4 t B; D - {% _! x! p6 f3 A: x3 r1 w4 B) O
- using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read)); b2 y& G% b7 u: H Z
- using (BinaryReader reader = new BinaryReader(fs))
0 F1 t2 y2 f+ p% @; { - {/ @! b. R* m* K6 X1 o
- // 读取头部
. n0 h1 V; U8 X4 D: x6 E' q. G - string header = Encoding.ASCII.GetString(reader.ReadBytes(4));* q7 [4 M! I o& C d9 B
- if (header != "FORM")+ _ e- b3 d+ ^2 @7 V0 P
- {
' r" F9 f* G4 w - Console.WriteLine("无效的.rl2文件格式。");
7 A( n, ^+ ?, s* { `+ s4 r - return;
1 ?2 {& y3 [, y4 b' i, ~& D4 D - }0 x q) U4 i' w3 s: f+ S4 X
* ~' R: }5 G8 r& B. H- uint backSize = reader.ReadUInt32();! J" C4 T# e {" u6 ]9 z4 M
- string signature = Encoding.ASCII.GetString(reader.ReadBytes(4)); E# M' B: {' p7 r5 K7 u u
- uint dataSize = reader.ReadUInt32();
; [+ z6 I: p/ r( c C& `1 M. @ - uint numFrames = reader.ReadUInt32();
2 n; _5 P; }7 ~ - ushort method = reader.ReadUInt16();6 m& s3 L( h* x$ b1 y7 b. {' t& [6 y
- ushort soundRate = reader.ReadUInt16();4 A6 t$ J/ l- g; f
- ushort rate = reader.ReadUInt16();" u- p$ X! P1 v, H/ w
- ushort channels = reader.ReadUInt16();
9 {/ u4 e" P+ s4 X L8 R" m - ushort defSoundSize = reader.ReadUInt16();& e- J* r# ^9 g! ^2 _4 w& G: q& x
- ushort videoBase = reader.ReadUInt16();
! W3 i' Z) N8 E1 q2 Y - uint clrCount = reader.ReadUInt32();
1 c4 c) A- g* b" ` - uint[] chunkSize = new uint[numFrames];5 F2 L' R, R/ E! h0 p& s3 r
- uint[] chunkOffs = new uint[numFrames];
$ i5 [1 p& L5 Y* @+ W' C: @; @ - uint[] soundSize = new uint[numFrames];
4 B! F( L$ z5 E; e- w( W: a( o
- _- r! U4 i Z7 \1 V- if (signature != "RLV2" && signature != "RLV3")
4 O! K, n7 k: u- }2 s - {
. R) r6 v- V+ t - Console.WriteLine("不支持的签名。");
" K3 _9 T9 A2 M; T9 V - return;
) ~7 x. f. ?* b6 Y - }
8 a: P7 L8 N8 u" R
" G3 |- T2 \# K5 }- // 读取块信息
% ~5 n G8 ]# i0 ^3 ^" C8 \7 R+ s - for (int i = 0; i < numFrames; i++)/ r8 w" }- H9 r3 d: V' m' r! d! ~3 B* O
- {" E+ @4 ^- Z8 M
- chunkSize[i] = reader.ReadUInt32();5 F0 e/ h3 L+ [: }8 u3 f
- chunkOffs[i] = reader.ReadUInt32();, t: ], T9 j' d B
- soundSize[i] = reader.ReadUInt32();
7 @2 S. b4 j k0 ?! q; J - }
( x; @8 n" {+ Q4 z: ?5 X" t
& r' Y6 G# X% C/ I- // 如果存在背景帧,请跳过它* b0 @+ y- S6 |
- if (signature == "RLV3" && backSize != 0)
% Y2 K/ {1 r4 |& b2 o - {
5 w1 ?& A/ i" C+ f1 y2 H2 M - reader.BaseStream.Seek(backSize, SeekOrigin.Current);+ G z1 s. l e1 i( f) t n
- }
& Z/ B* P E$ ]' |* L - 4 Y4 C6 I% o% p( K, `" \; ]
- // 创建一个WAV文件并写入音频数据. M5 s5 O+ o7 M0 B3 E; W
- using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create)))% G% }0 s1 z& A. [/ S
- {; t9 ]/ n+ R' b* G) U4 ]
- // 写入WAV头部. N2 b! C$ ], v7 Z9 Q) C) i3 e2 z) `
- wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));7 e" y# \! j* e; ^
- wavWriter.Write(36 + dataSize); // 总文件大小 - 8; G1 o) z* O9 \. I$ U/ w7 p9 k, J
- wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
. g: n, s# ^% U8 v0 {" b7 [ - wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));( O0 Z. f! B, N8 K; [- | T
- wavWriter.Write(16); // fmt块大小/ k( G4 E% a+ K/ \2 k- G
- wavWriter.Write((ushort)1); // 音频格式(PCM)1 Z% p6 w9 f- D
- wavWriter.Write(channels); // 声道数
( J. K' u8 k7 e - wavWriter.Write(rate); // 采样率
$ s' V9 a# E2 u) F/ I! \ - wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数' d( ^3 b) G0 r$ D. E
- wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数' e1 z) {" s) b# U4 P, l- x# D
- wavWriter.Write(defSoundSize); // 每个样本的位深度2 z- `" A! E) { r. D
- wavWriter.Write(Encoding.ASCII.GetBytes("data"));
" K' v5 Y4 T/ H4 E: s6 |3 U - wavWriter.Write(dataSize); // 数据大小' ~7 f; Y! Q0 d
- + N3 Y% k, K( X' @# N; ?% U
- // 从.rl2文件中读取并写入PCM音频数据1 L: Q* k9 ?( D! O" ~' I$ h/ O
- for (int i = 0; i < numFrames; i++)
) \8 k7 T. N ~: _ - {
. f* W& J W, ]: i8 Z - byte[] audioData = reader.ReadBytes((int)soundSize[i]);
1 G& `) `$ J* H2 j; } - wavWriter.Write(audioData);& A h+ c% {( e2 B# V# \
- }
* [% s- a/ S# U - }. W2 y0 M$ P0 w9 `7 x7 J
- }
8 b2 L# F' q+ _; M - }+ z0 Y/ u, E! K) H
- }
+ O' O; H5 j+ W& s1 C L+ D/ m3 X
1 C1 \% z& a: S" }- class Program
- u/ R, [& Q( j - {# }6 z4 v0 }2 Z1 j1 M( [/ z
- static void Main(string[] args)
! m/ H. g, N: l) ` n& X5 M - {
/ V7 q& x' D, K6 o - string inputFile = "N1275510.RL2";& b7 B& a; H3 e; u( V) M
- string outputFile = "output.wav";+ m1 ]; \8 j+ e5 W3 T6 Q7 n
- RL2ToWavConverter.ConvertToWav(inputFile, outputFile);- w+ R: S/ Y% c" g' x0 @4 O, ^" r
- Console.WriteLine("转换完成。");8 H/ `3 T8 \, A: L6 W$ z
- }
5 ]8 y: R- B( r - }
& Z% y3 x0 t, F; D* X
复制代码
, `2 N0 D! i1 o8 e+ q. H. c( n( P$ R8 C# B
, Y0 w" m( N) I ~" ~ V( m |