本帖最后由 shane007 于 2023-9-2 00:06 编辑 4 @4 \: M2 n! d3 k# W" N
$ [7 `6 z& W' r1 n9 I3 B3 s9 [
该游戏是scummvm支持的少数几款FMV AVG。# }6 t. D+ T6 x6 I4 o" [" j
视频采用了一种叫做RL2的格式。
# C7 E. ]. S( h: ^9 x参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,
( A- n2 J7 M# W7 w$ Q. Y8 Q6 F1 `) H然后用whisper语音识别之后就能配上字幕了。
: V) H; o( L; o- K此外,rl2格式用potplayer也能直接播放。
- P0 r: Z; O9 m" k2 k, W& n6 p, }" I2 w- l( y
文件格式
4 ^( Q1 E) Z6 C# chttps://wiki.multimedia.cx/index.php/RL2
$ H1 C- @8 _! o& G G) A5 A
8 D! c8 G! r" P! C- + 0 dword Header -- "FORM"
0 _) R7 \ h( t, M, D3 m6 q - + 4 dword BackSize -- size of the background frame" j$ P3 q/ M- S
- + 8 dword Signature -- "RLV2" or "RLV3"3 U: U* n4 f p6 q1 {
- + C dword DataSize -- size of the data past this point BIG-ENDIAN' B0 b; | Z1 v. w% U R
- + 10 dword NumFrames -- number of frames
2 Z" \% a8 O+ M; T - + 14 word Method -- encoding method. ignored, zero4 Y# j+ Y1 `8 U
- + 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound! k0 k$ l& r1 q3 S+ n
- + 18 word Rate -- sound sample rate in Hz# t) T8 g7 J; s
- + 1A word Channels -- number of sound channels
5 F2 ]8 n! q0 D# t. w( ~ - + 1C word DefSoundSize -- size of the single sound chunk. see notes below
+ ^4 ?; E7 ~. _' B& S - + 1E word VideoBase -- initial drawing offset within 320x200 viewport
1 e# E0 t& `0 d' {* o1 M2 f7 P9 `' W - + 20 dword ClrCount -- number of used colors" q- ]1 N4 Z h( R" ?# o+ R' ^
- + 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries
- l" U" a. X$ v8 E - -- if Signature == "RLV3" AND BackSize <> 05 t" x: ^. a; f1 v9 ?2 Q
- +324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding
1 c+ p& l. L, a6 z8 m( M0 c0 M$ Q0 Q3 s9 \ - --! N; i$ F( a( z" I+ Z3 _" n
- +xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video)
8 Q% N7 G+ B7 @ - +yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file, }% n4 `) H+ j2 `: E
- +zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk) l! I$ p& P3 v
- -- for each frame --
; j" ?* {% C, a O7 A - +xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio
- h: C0 w- }8 p& F9 c2 [$ c! \ - +yyy byte Video[...] -- compressed video stream
复制代码
2 M, ?6 }: g( ]/ o1 @! K参考代码(有问题,但可参考)- using System;
" `; L U$ y. C" ` - using System.IO;% M8 |6 {/ |5 @
- using System.Text;
8 \* S2 V5 B$ _7 A+ f
+ f) X* O3 O( g- public class RL2ToWavConverter
6 G5 E! p, r! B' F" H7 k - {
" E! i/ \2 ?1 G) h2 M: D9 R - public static void ConvertToWav(string inputFile, string outputFile)5 H" M, F) Z3 ]! O8 @
- {
- F& X! w( U1 T8 h) n9 S. o - using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read))/ ~8 [& Z5 V* X9 k9 l$ ?* ^! [1 L
- using (BinaryReader reader = new BinaryReader(fs))) n2 g1 u+ q9 v& t6 \
- {- o T0 t: W5 i% ]' @
- // 读取头部# o+ b" \! o7 a
- string header = Encoding.ASCII.GetString(reader.ReadBytes(4));4 H: f4 L! t# @$ F, Q) p: r* t
- if (header != "FORM"); @. Y2 C& r/ R
- {7 e9 T8 a; x6 j: b5 a5 e, y6 w6 {$ @3 l7 r
- Console.WriteLine("无效的.rl2文件格式。");
1 g$ X$ x( T$ J. D% s! i - return;% N O! X9 W. V) i
- }( M( R1 K: |; c
- 8 c4 X0 A' x, q$ Z7 ^
- uint backSize = reader.ReadUInt32();+ K2 ~- k" R8 _5 {0 R
- string signature = Encoding.ASCII.GetString(reader.ReadBytes(4));. v. }& t2 v4 k
- uint dataSize = reader.ReadUInt32();- d7 t& ` F" H: C: ?* \3 U9 ?
- uint numFrames = reader.ReadUInt32();1 i* [* W8 R1 ]: X$ q
- ushort method = reader.ReadUInt16();& j' ]/ _+ |6 G L$ c
- ushort soundRate = reader.ReadUInt16();
; C1 |2 v4 m- V8 y: r - ushort rate = reader.ReadUInt16();
1 T: i. G) j! a& M - ushort channels = reader.ReadUInt16();" M# r# {: E4 ?7 t! [" n0 F. V
- ushort defSoundSize = reader.ReadUInt16();
+ Q% |) e% \% Y - ushort videoBase = reader.ReadUInt16();& f0 P; N. |4 |- G1 }
- uint clrCount = reader.ReadUInt32();
$ s6 W& v; @& h. {; k* B - uint[] chunkSize = new uint[numFrames];* O* G B6 V' K+ i V1 p5 b
- uint[] chunkOffs = new uint[numFrames];0 m* Z. g U. I2 x+ \
- uint[] soundSize = new uint[numFrames];
# K+ J5 e) r$ t: ]6 C: Q) M1 S - : W6 h6 ^: Z% h3 g
- if (signature != "RLV2" && signature != "RLV3")# H3 @. x0 T4 m8 D
- {- O `& n; c$ M
- Console.WriteLine("不支持的签名。");/ {; A) ~1 k, u: }, A
- return;5 r& ]& Z( r/ T4 W
- }( x' v5 L' }% T$ t( F
) ^8 J1 [0 C4 ]1 ^- // 读取块信息
" g% }2 L3 t8 L2 M( d: ^ - for (int i = 0; i < numFrames; i++)7 V) \7 Q& w$ T
- {( m6 l$ z/ o h
- chunkSize[i] = reader.ReadUInt32();7 f- ]& I7 m: m6 s+ Z* b0 Y
- chunkOffs[i] = reader.ReadUInt32();9 _# l& p1 b7 e {8 m+ q2 i8 b
- soundSize[i] = reader.ReadUInt32();% A+ Q6 O/ W7 ~; m5 c
- }, N' p: x. w0 z* h4 h O
6 J* C$ P' X0 i2 Y6 {2 a7 j- // 如果存在背景帧,请跳过它6 q7 V2 o8 Q" `: K4 f0 B) ~. D" x
- if (signature == "RLV3" && backSize != 0)
( I" S8 G% b. P* e( F( V n# c0 ] - {9 V& {) \: U. ?' J
- reader.BaseStream.Seek(backSize, SeekOrigin.Current);% z# H8 n8 q! e" d) I* X, Z
- }: u+ P5 e7 ^( h/ b
; Y3 m0 n) Q# k/ W3 \# j- // 创建一个WAV文件并写入音频数据! ?8 F" A3 e$ r: V
- using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create)))
$ \" M V2 k, n8 M9 r+ K - {+ b) v2 _) ~7 x4 V1 Y- h
- // 写入WAV头部6 C p: o) K5 B) N# D( q3 \
- wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));
" I- z# ]* Q9 e" c - wavWriter.Write(36 + dataSize); // 总文件大小 - 8
% A4 s L) Q9 v; ~7 w0 p - wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));9 H# O: q) f2 G; x
- wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));
" r( i6 m0 K* T# x5 a - wavWriter.Write(16); // fmt块大小
, ]1 P- `# @1 H+ c2 g& A. B+ \5 ~ - wavWriter.Write((ushort)1); // 音频格式(PCM)) ?, r. e; z# u5 U: M f$ c5 ^
- wavWriter.Write(channels); // 声道数
, X. I* h# Z/ b" }! x( z4 I0 l2 E - wavWriter.Write(rate); // 采样率- q7 Q/ \5 s( _3 L* s! A5 y8 o
- wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数
8 l8 n! _( @3 J# x1 _! v7 H - wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数
& g- P4 Y/ d8 q4 T; ?) N - wavWriter.Write(defSoundSize); // 每个样本的位深度( N# a- W1 m0 j) P# q3 l
- wavWriter.Write(Encoding.ASCII.GetBytes("data"));) r0 u+ ?4 c( f8 l; ~, w/ d
- wavWriter.Write(dataSize); // 数据大小. ^$ E7 N4 }) {. B$ c4 B! ^) P) e/ n
' E! f; o1 i; `# C8 `6 a- // 从.rl2文件中读取并写入PCM音频数据
8 I- S/ c8 n; U# f8 x; k! V - for (int i = 0; i < numFrames; i++)
! [( I- C# }1 | - {
' Y$ a" ?6 G6 Y& U' w - byte[] audioData = reader.ReadBytes((int)soundSize[i]);
, m0 L* }6 x5 ^ - wavWriter.Write(audioData); ^# `! [9 G+ D# p' e
- }8 {4 g4 o% n5 Z6 q* J, S. t) x
- }
# J! l m/ k( p e% z! Q - }
9 X% V. q1 G. w9 r/ I5 `. n - }
- G0 k- [" b9 t - }
) p; D" d; N( U4 l7 O9 B9 N" O% ^1 T - 2 c! I, @& K* X) C F" X
- class Program
8 R: a! L& O6 ? - {
2 H/ \! c+ q5 S5 v3 H - static void Main(string[] args)
! B* T' V( H8 {; ^6 N+ u% y! H7 H - { x: f9 h. Y2 ]1 \, r$ z, q1 p* Q. j7 i
- string inputFile = "N1275510.RL2";, L- B; ^5 |6 R% n! f, b
- string outputFile = "output.wav";/ R8 A% D& p7 N$ s; }0 e
- RL2ToWavConverter.ConvertToWav(inputFile, outputFile);
$ F1 z( j" O" s2 {, s - Console.WriteLine("转换完成。");
; v! M/ p* u" |' d/ C. T% b. a& i - }: q2 J8 ~& Z9 ^* X% t" L9 A
- }! N0 _' {6 |% I& m: ~! h
复制代码 , C9 |6 Q: m9 H7 ~3 X
1 }8 b# `% U* \5 U) H0 l8 Z- q& z3 W. y8 i2 j( a5 b
|