冒险解谜游戏中文网 ChinaAVG
标题:
【Scummvm汉化 #6】 Voyeur (CD - DOS) 偷窥 音频分析
[打印本页]
作者:
shane007
时间:
2023-9-1 15:34
标题:
【Scummvm汉化 #6】 Voyeur (CD - DOS) 偷窥 音频分析
本帖最后由 shane007 于 2023-9-2 00:06 编辑
0 a. r( z* f( T1 ^/ Z/ Y' {
' W% Y) x0 f/ G8 G
该游戏是scummvm支持的少数几款FMV AVG。
) U! n# D M$ `0 x, \$ v! U r
视频采用了一种叫做RL2的格式。
% v; D4 L! Q, U+ h4 w# v) Z, l
参考以下格式和Scummvm中的代码,可以想办法将RL2中的raw data部分转换为wav,
& b' m( I' R' v" {, ]
然后用whisper语音识别之后就能配上字幕了。
( I4 D2 p6 R7 |9 z( A, \
此外,rl2格式用potplayer也能直接播放。
; S" N6 c) N. M- `
" h8 T" Q0 \4 n8 d: X
文件格式
9 F# s# p5 x) M1 V3 V+ i
https://wiki.multimedia.cx/index.php/RL2
$ b* U* x1 K# |$ S4 J
0 \* n* h1 |' V' O6 S* U2 ^ s5 j
+ 0 dword Header -- "FORM"
6 A0 C+ O e; G+ S0 H0 G" [
+ 4 dword BackSize -- size of the background frame
/ G S# S0 p8 G+ z% r% S* ^ w& B
+ 8 dword Signature -- "RLV2" or "RLV3"
# g) J5 Z; R" i" I- d
+ C dword DataSize -- size of the data past this point BIG-ENDIAN
8 f, M; R9 m3 U O$ B, i
+ 10 dword NumFrames -- number of frames
* ]' t( y: S, h0 C6 H( y
+ 14 word Method -- encoding method. ignored, zero
' d4 t" @) a4 y- h4 W
+ 16 word SoundRate -- sound sample rate in some obscure format. zero means no sound
& \7 p) n- T) `& ]/ k& |4 P
+ 18 word Rate -- sound sample rate in Hz
2 B f. F+ l h
+ 1A word Channels -- number of sound channels
1 \) x* s5 d1 A) V
+ 1C word DefSoundSize -- size of the single sound chunk. see notes below
( G8 w; B, |* o& N1 t& ?7 t0 U
+ 1E word VideoBase -- initial drawing offset within 320x200 viewport
: Y: [; s* S6 f/ p
+ 20 dword ClrCount -- number of used colors
0 ?9 X b1 R$ i3 [: R( S) B
+ 24 RGB Palette[256] -- 256 RGB triplets. full 8-bit entries
- F) s2 \9 z3 B! P5 E- I4 ~& Z$ I( C5 p
-- if Signature == "RLV3" AND BackSize <> 0
! U6 c, H, I( M( X
+324 byte BackFrame[BackSize] -- encoded background frame. ignore VideoBase during it's decoding
" u, Z6 ^" ^! F0 s) @& R- X$ h
--
" }, F: F4 x$ Z% X' n7 P( {7 f
+xxx dword ChunkSize[NumFrames] -- complete size of the chunk for each frame (audio+video)
* u7 \( X% k3 g1 E4 O' ?
+yyy dword ChunkOffs[NumFrames] -- offset of the each frame chunk from the start of file
. `8 F h+ H* p) v" B) t! S
+zzz dword SoundSize[NumFrames] -- size of the audio portion in the frame chunk
' @& e" X# L" p0 ~
-- for each frame --
4 E4 S2 [1 a, ]
+xxx byte Audio[SoundSize[frame_nr] & 0xFFFF] -- raw 8-bit audio
5 c2 S- E6 C0 Z% n. ?/ O" Z
+yyy byte Video[...] -- compressed video stream
复制代码
9 u. v1 W/ w, {% w& v
参考代码(有问题,但可参考)
using System;
8 y3 ?" X, I( S
using System.IO;
1 ]4 O# }' s( m- _! p- G( H( @- N
using System.Text;
1 W5 [ L* l& i
: h2 Q. t6 d1 G' }
public class RL2ToWavConverter
" N* ?" t. f! n- ], L
{
9 ]- O; K/ f+ Q# b. `) C9 {
public static void ConvertToWav(string inputFile, string outputFile)
* c7 ^' g# N6 G; f$ ~+ m
{
; W7 |. E% H3 C
using (FileStream fs = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
1 {3 Y5 ~& L2 l; N! W) V
using (BinaryReader reader = new BinaryReader(fs))
; l ~2 X) Z: n7 @
{
( l. n! h& s/ |& x: K. P. v
// 读取头部
/ x3 S2 p+ @4 `* m) u6 E7 K
string header = Encoding.ASCII.GetString(reader.ReadBytes(4));
' b: c" d2 ?: A7 q5 M; O) B
if (header != "FORM")
1 _; S3 ^, M9 G5 u
{
2 N8 R- u) m7 _" I( q4 T
Console.WriteLine("无效的.rl2文件格式。");
9 i! ^) k2 A# P C
return;
& j% P8 h! r5 f5 N" J
}
8 E- k1 M. A; M* l% G
; C2 p- k5 _* A u
uint backSize = reader.ReadUInt32();
$ j8 Y2 H2 x* ]& h3 x) f9 [
string signature = Encoding.ASCII.GetString(reader.ReadBytes(4));
# y; k& D/ i1 n0 Q
uint dataSize = reader.ReadUInt32();
& I- l5 [ g5 ^6 |8 S$ ?9 F4 o
uint numFrames = reader.ReadUInt32();
, Z: o& D" V0 y. ], }
ushort method = reader.ReadUInt16();
0 I9 E9 x3 n% \9 w+ Q
ushort soundRate = reader.ReadUInt16();
' A9 v% s6 }) T+ X9 z
ushort rate = reader.ReadUInt16();
/ ~3 l6 v+ ?+ R! j
ushort channels = reader.ReadUInt16();
" h. s( q) @. l1 F' w
ushort defSoundSize = reader.ReadUInt16();
# z6 h1 F' W: S2 y, E) C
ushort videoBase = reader.ReadUInt16();
1 S- |" Y* Z3 ]
uint clrCount = reader.ReadUInt32();
# s2 E6 G, S* J9 S
uint[] chunkSize = new uint[numFrames];
; u- V. r% J$ ]+ A* @
uint[] chunkOffs = new uint[numFrames];
! x! h- ^; @, l) [! y& `
uint[] soundSize = new uint[numFrames];
: O9 U, s6 k. s3 [1 p5 Z3 L
, h) s' r1 W% z# A! s
if (signature != "RLV2" && signature != "RLV3")
: Q! X$ I& K0 ^, Z! B) }1 M7 K7 Z
{
: b/ y8 r- y; h( U
Console.WriteLine("不支持的签名。");
/ K" A u+ P# m% ^
return;
3 G# h4 O" y0 p# ]* u
}
) q; U) E/ ?; P# u' @6 o, ^$ D
. H0 i m+ l0 V% j
// 读取块信息
: t; I: U s2 R0 s- X! Y! V
for (int i = 0; i < numFrames; i++)
: G+ H P% e5 y- \
{
5 A! r4 @5 ~4 ?2 L% n* B/ o$ i. R. x
chunkSize[i] = reader.ReadUInt32();
! c7 ]# a5 u, S! S& b7 G
chunkOffs[i] = reader.ReadUInt32();
& R4 s- a9 k: f& Q7 Y% j% R; ?& S9 E8 y
soundSize[i] = reader.ReadUInt32();
4 u7 }0 }- g- K: H3 j. s* {
}
& ^( t% p* G; l) Q1 H, ~7 w4 Z$ M) c
- I" s" x m9 J, N$ H8 @% O4 h
// 如果存在背景帧,请跳过它
% B9 u1 `! R& W9 i# N6 P% h
if (signature == "RLV3" && backSize != 0)
: t4 q0 @9 v! f1 ^9 w9 D7 S
{
( |% ~4 I4 {3 R2 G3 _
reader.BaseStream.Seek(backSize, SeekOrigin.Current);
( s: o: D$ `- _; ]0 ]% m1 ^
}
4 @. I. q2 s4 I Q3 A: P# ?
4 |4 O* Y U& T8 g/ {
// 创建一个WAV文件并写入音频数据
# R9 T7 L( E5 z% t6 l" x
using (BinaryWriter wavWriter = new BinaryWriter(File.Open(outputFile, FileMode.Create)))
; z' p/ o+ ]' b9 I3 Y' H: X
{
% e [- W" N9 y( D( Z/ Z& G8 B
// 写入WAV头部
( ]) l' c5 a: n2 N
wavWriter.Write(Encoding.ASCII.GetBytes("RIFF"));
% Y6 B# I) h; P2 [
wavWriter.Write(36 + dataSize); // 总文件大小 - 8
4 P- O* b6 T! U
wavWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
% I5 |7 _+ |2 _+ P5 ]( i, X
wavWriter.Write(Encoding.ASCII.GetBytes("fmt "));
6 j$ N. C' y3 v
wavWriter.Write(16); // fmt块大小
0 K; ]) M1 l& J5 W" u+ @) z2 l
wavWriter.Write((ushort)1); // 音频格式(PCM)
% F0 Y8 s5 d( h& ~* B
wavWriter.Write(channels); // 声道数
2 ^0 W# S( o+ h% G
wavWriter.Write(rate); // 采样率
, A. K7 E/ i. u5 P
wavWriter.Write(rate * channels * defSoundSize / 8); // 每秒字节数
/ G+ R3 _1 _" J& @6 L
wavWriter.Write((ushort)(channels * defSoundSize / 8)); // 每个采样点字节数
4 b% d+ P9 U8 o
wavWriter.Write(defSoundSize); // 每个样本的位深度
+ f0 k2 k8 v: Q2 s0 o. b( x" O
wavWriter.Write(Encoding.ASCII.GetBytes("data"));
1 ?5 V! @* V/ i7 l: t2 ^
wavWriter.Write(dataSize); // 数据大小
; H# C, b, }+ f3 F& b3 T
4 o3 i7 H/ @. `# Y( m
// 从.rl2文件中读取并写入PCM音频数据
1 y: }! M- [3 Q q
for (int i = 0; i < numFrames; i++)
5 k9 ~/ ^4 T0 Z5 q' f5 J3 T
{
3 k' L5 i" L/ O" U6 p: F/ Z# k
byte[] audioData = reader.ReadBytes((int)soundSize[i]);
% A2 v" y* R" z
wavWriter.Write(audioData);
( M6 P& T! W* s4 Y
}
2 E, M/ Z* R! G- ]- J
}
7 S7 ?& Y1 l4 g9 H2 j
}
( w3 l- m" b5 `7 Y) G& I
}
& c3 P3 A5 z% V! }. F( _
}
' }5 D4 A' O6 {4 ?5 l8 J
8 j& [! }7 }9 l
class Program
! \8 C4 ?/ s1 V s7 s% r
{
9 d- l7 w) P% X, ?- D6 X
static void Main(string[] args)
- M, m2 }9 w+ \$ V/ O3 K
{
, ]/ i' b0 g/ k( c1 f& n
string inputFile = "N1275510.RL2";
7 M$ u% ]6 C6 i f
string outputFile = "output.wav";
4 a: d4 [2 v- D
RL2ToWavConverter.ConvertToWav(inputFile, outputFile);
6 w9 S' c" A7 a6 ^( R
Console.WriteLine("转换完成。");
6 Z# Q9 @5 q7 v7 A; b: E
}
; k7 Q x; Z" M- f* ~
}
' g4 ~$ }6 Z9 x5 r9 b1 N. s
复制代码
4 e. P4 @" T& h, ?
1 u$ ?! h @' F. x- Z+ H- Q
2 r0 e, B' }- x: Y6 P. ` j
作者:
星之韶华
时间:
2025-4-4 01:08
学习学习一下
欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/)
Powered by Discuz! X3.2