本帖最后由 shane007 于 2023-9-2 00:06 编辑 / |3 l1 E, S: o# b( U! c$ j. \0 f
- t+ b' G2 R1 j" x8 ?: L
该游戏是scummvm支持的少数几款FMV AVG。/ h" n# T2 k6 v8 I0 z" u, A
视频采用了一种叫做RL2的格式。
( M& W% J( y* D0 _参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,0 ]& I5 v1 R7 E# D4 j- J
然后用whisper语音识别之后就能配上字幕了。, A! S/ W, n6 o# X. r6 D
此外,rl2格式用potplayer也能直接播放。
3 w# h% N/ o/ s/ V! L0 r2 u
7 E( m1 W; K3 B( t2 h) Q2 B文件格式0 r9 P& Y/ i8 r8 L0 S# D/ \
https://wiki.multimedia.cx/index.php/RL2
* z9 J' E# a4 {0 Z1 O+ p* }' j
* I2 G1 P! Q+ g( e% G- + 0 dword Header -- "FORM") s& w9 Q" t% H o
- + 4 dword BackSize -- size of the background frame+ v9 m' w! J' [* S W
- + 8 dword Signature -- "RLV2" or "RLV3"- F8 z/ q8 o7 D9 J9 E5 C6 n) T
- + C dword DataSize -- size of the data past this point BIG-ENDIAN
/ b9 W4 b; C- h! X - + 10 dword NumFrames -- number of frames: [; q7 @3 }) C: s8 A% c2 D" F+ L0 G% V
- + 14 word Method -- encoding method. ignored, zero
6 M7 ^' n% ?' Z0 c4 D - + 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound' ]% B+ r# x3 x0 w; F; X
- + 18 word Rate -- sound sample rate in Hz* G6 y( `9 Q- x# k
- + 1A word Channels -- number of sound channels- C; z- v; Z& }& `& l' m# G
- + 1C word DefSoundSize -- size of the single sound chunk. see notes below
5 n8 \" k' M0 }- q4 e4 x - + 1E word VideoBase -- initial drawing offset within 320x200 viewport
1 u" z% _& b/ ~7 y5 F( M, D: n$ l: e" P$ l - + 20 dword ClrCount -- number of used colors
' `. D6 v/ c1 Z! \ I6 [5 { - + 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries4 b3 Z" ~" ^4 ?+ r6 | @
- -- if Signature == "RLV3" AND BackSize <> 02 i0 b4 h: `$ r6 `7 C0 V% i
- +324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding3 F+ R, X) I/ V. L1 Z0 [
- --5 y1 V& r$ y) K. N N/ y% m
- +xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video)2 k1 T8 K: y9 k" G) ?
- +yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file' R7 m: V1 d: ]0 r+ O# u2 C
- +zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk! c% l1 K- t' o5 Y9 L( Z" W
- -- for each frame --
! D. ]* L. S# J0 d* h - +xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio
" L+ @3 y$ @2 K( [ - +yyy byte Video[...] -- compressed video stream
复制代码
8 l, Y; W) h; \# w5 P: D1 c参考代码(有问题,但可参考)- using System;& q" e1 t! U* X R' m( g
- using System.IO;
. W4 a# `; X' P - using System.Text;
6 i7 G8 e8 V. g. q0 c! S5 K t
, M+ U1 P5 B' {+ Q9 \. C( k2 N2 F- public class RL2ToWavConverter. L9 z* m& q) x
- {
5 c ?6 }5 \: m+ G! p - public static void ConvertToWav(string inputFile, string outputFile)
% f% K" `) b$ s$ ] - {
2 V; e4 j+ l' J - using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
$ k" z4 O2 ], `% M - using (BinaryReader reader = new BinaryReader(fs))" }' u4 q3 b. E
- {
" C; w, n: y I3 B7 c- f - // 读取头部4 W. d( ]% F- V# C$ ] M
- string header = Encoding.ASCII.GetString(reader.ReadBytes(4));
& z7 B/ Y9 C. X# P - if (header != "FORM")! c8 t+ d2 I( [7 h4 B
- {( @! p: ^$ J5 b
- Console.WriteLine("无效的.rl2文件格式。");& g) @" w6 p! i* M) ~7 D3 h+ g
- return;
- F5 `$ V5 `1 m" V5 k9 O' K - }
, w O4 w3 `8 N& B& I/ _ - & b4 z6 j7 h0 U7 ^" O
- uint backSize = reader.ReadUInt32();
" \6 u# O, A9 p7 R, a9 |' l5 n - string signature = Encoding.ASCII.GetString(reader.ReadBytes(4));
0 i: Y0 P* x. c( W: g/ ` - uint dataSize = reader.ReadUInt32();
# N- A9 M4 x$ W, w! k0 t - uint numFrames = reader.ReadUInt32();. c2 ]2 `! ]+ W" q9 N
- ushort method = reader.ReadUInt16();
! S3 w) |' U, l* } @ - ushort soundRate = reader.ReadUInt16();. o8 g+ S) w7 @
- ushort rate = reader.ReadUInt16();/ ?+ V7 Q, Q6 _# R' ^$ ^
- ushort channels = reader.ReadUInt16();6 F0 D, ?( f2 t# m
- ushort defSoundSize = reader.ReadUInt16();$ w. q- g% G' s% \( P
- ushort videoBase = reader.ReadUInt16();% s5 w/ Q& t" U: P$ w1 F2 ~! x
- uint clrCount = reader.ReadUInt32();; x1 \( h1 _1 t% `' T
- uint[] chunkSize = new uint[numFrames];2 x& ~3 j8 B& G4 X$ X( ?* x& e( Y$ [
- uint[] chunkOffs = new uint[numFrames];
+ U' C: P0 Q ?! Z0 p3 \& H - uint[] soundSize = new uint[numFrames];
% K; d! r* u1 C3 F8 m
; t2 @5 Q; L1 m& \4 ^) V- if (signature != "RLV2" && signature != "RLV3")
O' [' E2 b9 A& z0 Z8 G' | - {. E P% }' L- Q$ W1 ~- M d8 i1 g
- Console.WriteLine("不支持的签名。");& U. c$ Q" e5 V4 Z
- return;
6 e7 C6 x" _$ m3 o( K! f( v1 w9 b, B6 ` - }
9 j8 Y) V7 y+ l1 N5 w. u - 4 g d( L1 L: R9 b8 q# M- r
- // 读取块信息
? b8 T& A2 q& o3 v8 d6 p - for (int i = 0; i < numFrames; i++); U* I" Q6 ^9 Z* r3 q) {& N' d( q
- {1 ~% M: A( d$ v, N$ {( e$ u# E4 U
- chunkSize[i] = reader.ReadUInt32();
5 R! v9 ]- d' S) z - chunkOffs[i] = reader.ReadUInt32();2 B8 M+ R# X/ K2 O$ Z
- soundSize[i] = reader.ReadUInt32();
' O: J c3 X5 `5 G - }, C C2 X! k- b" M+ Z1 ?
- , w {: K) W( K0 [5 }' }) K
- // 如果存在背景帧,请跳过它
; k9 q/ c. p1 K# B1 O - if (signature == "RLV3" && backSize != 0)
1 S- W+ {8 r% I - {4 b6 A# x. Q2 W Q
- reader.BaseStream.Seek(backSize, SeekOrigin.Current);$ t: y; @! u- H" I0 s
- }
3 F! n& e& W; t) E$ T
9 X5 _0 Y9 B; J4 ]) @- // 创建一个WAV文件并写入音频数据3 C0 V- v6 \/ L: M, @
- using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create)))8 J( y( w' X9 n. d, L
- {
( K8 v5 C5 h4 b, y- {2 U+ Z: B - // 写入WAV头部# z; @" G0 j5 d3 C) N: n! D
- wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));; g1 K7 L& } \# ` _
- wavWriter.Write(36 + dataSize); // 总文件大小 - 8
e/ P9 c7 N- q( t7 j - wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
( T5 z, P; v6 H; Y. f' v. {4 {" ` - wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));) y( j' ]- b. |9 `+ H% X
- wavWriter.Write(16); // fmt块大小
[3 k [+ ]! b7 { - wavWriter.Write((ushort)1); // 音频格式(PCM)8 z& I8 @5 d' `
- wavWriter.Write(channels); // 声道数
& e6 C& ?5 m+ p2 U: e5 g - wavWriter.Write(rate); // 采样率# J4 U l2 u6 x2 V X
- wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数
! T2 Y w4 ]7 [/ p! G+ J - wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数
) b8 L/ M, L8 L1 D6 W' l - wavWriter.Write(defSoundSize); // 每个样本的位深度
3 U+ n" E. w; d" D - wavWriter.Write(Encoding.ASCII.GetBytes("data"));: A5 K2 n9 W5 |* B7 y
- wavWriter.Write(dataSize); // 数据大小
& s7 q& |' l8 h3 a% z/ ]- ^
: z4 W8 U) Q, p9 q% N0 b7 p2 Z- // 从.rl2文件中读取并写入PCM音频数据( `. e7 I% p" ?' J5 A' f
- for (int i = 0; i < numFrames; i++)
3 V O4 }# x$ U+ S+ O - {7 N" f* ?# [' j& X# h! a! g
- byte[] audioData = reader.ReadBytes((int)soundSize[i]);+ p; e; J, O3 p: ~6 D# s W8 t8 N0 ?
- wavWriter.Write(audioData);/ i# V* `9 t$ m7 {) N
- }) ?$ {3 ?7 A. W" z+ r& Q* o) O
- }
* w( z4 {# Q6 H, q9 \ - }- q+ Y2 u L1 `0 M3 z6 P
- }* t U# s) q2 v, X
- }
# T. f2 x. g0 B/ i$ B/ s8 F - 2 N/ U5 o1 w* K: H( F2 K1 l
- class Program
/ U+ w9 f" G, `" C3 N - {2 e0 z8 _: j7 G* B" I
- static void Main(string[] args), o9 Z s& r; d+ H. _, ~
- {" L# Z, }: \! E; Y W: k' W: t
- string inputFile = "N1275510.RL2";
) t! q$ v g. d6 V - string outputFile = "output.wav";8 h# w6 ~9 `: H8 | w2 P; }( I
- RL2ToWavConverter.ConvertToWav(inputFile, outputFile);! H# ~/ K/ i1 ?7 M1 C; x
- Console.WriteLine("转换完成。");
0 h t- X, k( ?' K9 A - }
: J: X: h- J; s. O7 @3 } - }
% _* o8 Z, @/ X
复制代码 9 e8 l$ ?! ^- Q, k. l
8 W' T- u5 Q, e) ]
8 n6 ?9 o8 A' _5 V |