冒险解谜游戏中文网 ChinaAVG
标题:
【Scummvm汉化 #6】 Voyeur (CD - DOS) 偷窥 音频分析
[打印本页]
作者:
shane007
时间:
2023-9-1 15:34
标题:
【Scummvm汉化 #6】 Voyeur (CD - DOS) 偷窥 音频分析
本帖最后由 shane007 于 2023-9-2 00:06 编辑
3 ^4 |. o* Q0 b5 B" O/ S
! n# m$ b3 [, M/ B+ W+ v$ K s
该游戏是scummvm支持的少数几款FMV AVG。
& o' _; `6 p0 F1 f* _0 V
视频采用了一种叫做RL2的格式。
. ?' `) J) Z) x! R
参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,
. y, \, A; |( b+ ^# H7 t4 I1 ?
然后用whisper语音识别之后就能配上字幕了。
+ Q* f) b0 F- I
此外,rl2格式用potplayer也能直接播放。
+ Q: B, I9 E: O% U6 `" Z
9 E: h; z3 p% S
文件格式
/ x9 n; b* a) O' I( c8 Q+ n
https://wiki.multimedia.cx/index.php/RL2
5 r% w) N/ T; b
& ~; O3 l" I* T8 k% K/ e
+ 0 dword Header -- "FORM"
4 X1 |+ t7 U: T/ o; }
+ 4 dword BackSize -- size of the background frame
5 S4 A6 }. w6 \& m9 O
+ 8 dword Signature -- "RLV2" or "RLV3"
) A$ P/ a8 J* P" G
+ C dword DataSize -- size of the data past this point BIG-ENDIAN
# i( |! ~9 @5 D
+ 10 dword NumFrames -- number of frames
0 e0 \3 c% ?- J7 b" @/ r
+ 14 word Method -- encoding method. ignored, zero
( U' y7 w8 Z; |/ W/ \* f) D
+ 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound
w' C' _+ }( c" \4 l; F! e
+ 18 word Rate -- sound sample rate in Hz
, _- T5 m$ ]8 z2 ^; \
+ 1A word Channels -- number of sound channels
6 s( x' }4 @3 |1 z% |5 I
+ 1C word DefSoundSize -- size of the single sound chunk. see notes below
7 B% H e; z+ T$ x3 B. m8 J8 z
+ 1E word VideoBase -- initial drawing offset within 320x200 viewport
: I2 n% O$ ?# }: b+ [/ }5 m
+ 20 dword ClrCount -- number of used colors
4 o8 L8 T: r4 C) i" c3 Y
+ 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries
9 r0 V+ v, }3 k, P. b8 R" ?
-- if Signature == "RLV3" AND BackSize <> 0
. x' ?5 U( H+ u2 T
+324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding
) |, Q0 w$ d6 U+ u' {' Q- s8 Z2 d
--
3 ?" q$ V% }' U6 n# B
+xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video)
: J1 C1 ?5 ~" l% H) v
+yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file
5 P8 k7 V4 ]9 F& M; c
+zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk
% Q4 P6 x* g7 X! a% c4 x$ ^
-- for each frame --
6 M* c) a% p* A! d* [' Q" o4 f
+xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio
# ~+ _% z& |0 ]+ e; Q/ M: L _
+yyy byte Video[...] -- compressed video stream
复制代码
9 K h; x9 }( A. N* T: O8 m; \
参考代码(有问题,但可参考)
using System;
- b1 C6 k% C+ v* p9 Q9 z
using System.IO;
6 g& P; R4 C8 {( F" @
using System.Text;
! g5 g5 k& Y; D2 G- O# y. D$ J$ Z
6 f/ T5 B) @* t' a
public class RL2ToWavConverter
7 h' V( X) _3 I8 @! H* W
{
' P; n. T5 E8 s- A; G- h
public static void ConvertToWav(string inputFile, string outputFile)
9 {( e( ~7 F+ `# J4 ]) {
{
# o& L6 M% t9 i2 [
using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
' ]6 J1 T8 [5 f' @/ @7 ?& z, k
using (BinaryReader reader = new BinaryReader(fs))
4 d; D: f/ o( f
{
6 _1 v. S: i6 q1 U; ?5 k$ B
// 读取头部
p# t4 s7 i* s. Q. W5 V; j
string header = Encoding.ASCII.GetString(reader.ReadBytes(4));
$ V/ k5 t; f; U; I
if (header != "FORM")
- l# @9 v, a! |/ _! J: I% q
{
& C3 q( N! ^! R7 i5 _! F: S; b) P
Console.WriteLine("无效的.rl2文件格式。");
1 \4 I! e! V; U2 ]7 d8 s; U4 ?8 k
return;
5 q& G3 L; ?: T% S8 C
}
9 |+ j4 B# H( l8 r% L. z
/ j- f; S+ @6 K0 t9 v/ p9 r) G" ^, J& N
uint backSize = reader.ReadUInt32();
5 c8 m6 f6 i) y2 X' p4 ~ l
string signature = Encoding.ASCII.GetString(reader.ReadBytes(4));
" e5 Z- j* G0 L6 `
uint dataSize = reader.ReadUInt32();
: i8 Z, V9 V: o- {* ^
uint numFrames = reader.ReadUInt32();
* a! O+ s& c9 S
ushort method = reader.ReadUInt16();
: X6 a+ C* j# J( A( M8 x% ^
ushort soundRate = reader.ReadUInt16();
" U5 ~/ X$ }# W8 i: ~/ a
ushort rate = reader.ReadUInt16();
- P& m, Y- e1 X. _$ J) A) [0 o
ushort channels = reader.ReadUInt16();
0 f( ^( N" N1 W/ i/ o5 @
ushort defSoundSize = reader.ReadUInt16();
- V5 x: B, R9 ~: l* O( C
ushort videoBase = reader.ReadUInt16();
' C% {7 w! F: ] v& x
uint clrCount = reader.ReadUInt32();
5 O0 q9 m! E) H/ P5 m* g% G- S
uint[] chunkSize = new uint[numFrames];
4 f# H' C3 p8 L" A A3 D
uint[] chunkOffs = new uint[numFrames];
$ ~# [ X; Y2 d
uint[] soundSize = new uint[numFrames];
|% \. b4 f5 @$ ^# Q _: [
+ e- B* A" \2 f
if (signature != "RLV2" && signature != "RLV3")
8 O% W" n# R- s/ J! A4 v R
{
: I) Q; s1 K6 F Z9 H2 g
Console.WriteLine("不支持的签名。");
6 b# f! _. N$ P1 S2 ?5 G' o
return;
, I. |# W2 p! B/ Q2 ?
}
6 ?% |9 r# ?6 G
) S# |% ^! S) `1 o) m
// 读取块信息
: T% p0 F5 ~0 C- F1 U U G
for (int i = 0; i < numFrames; i++)
# g* q& ]3 y- _, w/ q7 V" |
{
4 o1 n( t# w `/ i8 T
chunkSize[i] = reader.ReadUInt32();
, O1 Q3 a2 K0 d t: }- \
chunkOffs[i] = reader.ReadUInt32();
+ E7 m; ^/ f2 m
soundSize[i] = reader.ReadUInt32();
% D# l( q8 n5 ~' a/ @& Q' ?
}
8 y5 s6 r F' A+ K! x7 x% ], W
3 u8 ~. F6 h* J% L- |& X
// 如果存在背景帧,请跳过它
y" g p: n( n2 k7 ?
if (signature == "RLV3" && backSize != 0)
+ u% O& k! o* \! o' S
{
3 Q" l% M6 [/ D
reader.BaseStream.Seek(backSize, SeekOrigin.Current);
4 f1 ]+ k3 X- v2 }' B
}
. F' X4 G. c: Y1 l5 C3 Q
* z& ?( H! F( g3 a% F) G k; Y
// 创建一个WAV文件并写入音频数据
% G) X9 t4 m' Z3 E! f q
using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create)))
5 \& V) s9 i; Q7 ]
{
+ t1 h/ G7 i4 s5 d- T+ c, }# v' V2 D. R
// 写入WAV头部
w/ u- _, v- F1 m" G+ M. [
wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));
! M8 z& J8 W6 h* J& V7 C! H# f: z
wavWriter.Write(36 + dataSize); // 总文件大小 - 8
( l+ _' c& ]" a1 ^. x# u
wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
( C0 E/ G+ n& {0 c9 B0 E: `- F. ~
wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));
1 e \! n/ [! Y1 e
wavWriter.Write(16); // fmt块大小
5 g! t. b$ ], f7 l
wavWriter.Write((ushort)1); // 音频格式(PCM)
% x3 b7 p1 _: {/ @+ K# l6 F) @5 |( I' j
wavWriter.Write(channels); // 声道数
9 F: M7 T- E+ ~5 J; ~
wavWriter.Write(rate); // 采样率
. Q8 `; s5 T; f% i& G
wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数
) n: n9 A( p, S4 z- R8 Z( t. j
wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数
* \* P+ Y* v+ S8 v. t
wavWriter.Write(defSoundSize); // 每个样本的位深度
4 R& m1 f$ ]; x1 Q) J1 U1 R. ?
wavWriter.Write(Encoding.ASCII.GetBytes("data"));
9 N9 V* D, R5 D! u; |
wavWriter.Write(dataSize); // 数据大小
& R7 l! Z1 r9 H2 f/ h( D9 a
0 r* j3 H, \& u# I, V* I. n
// 从.rl2文件中读取并写入PCM音频数据
4 u: }0 H c, {
for (int i = 0; i < numFrames; i++)
7 c) I m' X6 M
{
% ^) L; _% \+ k; q; a1 U" R) U7 ?
byte[] audioData = reader.ReadBytes((int)soundSize[i]);
' ~% K! x0 b C3 I' i
wavWriter.Write(audioData);
9 D2 } f! D; P- e! V. |9 h( V
}
$ q9 S6 e4 ^/ g+ y# Y0 S) O' q
}
5 G1 ?, Q0 \3 ~- r
}
* P* P! N0 X& W. R
}
/ d) E T b( R; d2 s; T
}
' ]0 n4 E. s% F" z0 t
' ]' m: \6 T6 E
class Program
& Q% x6 x1 o# j
{
N, Q6 I$ B" E! t% S4 j
static void Main(string[] args)
& c% b; k+ D! Z3 t/ U% h
{
4 F$ y, C0 r' h" b
string inputFile = "N1275510.RL2";
) C/ E- [* H# |- w% c
string outputFile = "output.wav";
; @& j$ N* L m0 [% N& H! w# B
RL2ToWavConverter.ConvertToWav(inputFile, outputFile);
/ h. G3 v& c1 L% I! R- d- H+ l
Console.WriteLine("转换完成。");
4 p8 N# G; `6 y7 P$ {# B$ Z5 G- R
}
. K' M. q! Y S) D
}
! F. K# J2 S: u) l) L k7 f2 a
复制代码
! f6 o# A- y9 f) J0 \* u+ P6 Z
/ |, B% \4 H5 A' i4 _; F! d$ _
6 s$ ]: M0 a4 Y/ w8 r$ _
欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/)
Powered by Discuz! X3.2