自制英语学习机
试了很多软件,感觉“xxx背单词5白金版”功能比较强劲,但只能点读单词和例句,能不能自己做个点读句子的学习机,Let's go!
工具嘛,ring3下用delphi就够了。
存在问题:
1、发出声音用什么函数?如果用function PlaySoundA; external 'winmm.dll' name 'PlaySoundA';那么就需要wav格式的数据。
2、我有很多mp3,如何把mp3格式的数据转换为wav格式?
3、我还有很多相应的.lrc文件,如何根据其中的时间信息定位到MP3中相应的位置?
围绕这些问题,我首先开始恶补MP3格式的知识。这里提供给大家:
4个字节的帧头FRAMEHEADER 格式如下:
AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
A 11 (31-21) Frame sync (all bits set)
B 2 (20,19) MPEG Audio version
00 - MPEG Version 2.5
01 - reserved
10 - MPEG Version 2
11 - MPEG Version 1
C 2 (18,17) Layer description
00 - reserved
01 - Layer III
10 - Layer II
11 - Layer I
D 1 (16) Protection bit
0 - Protected by CRC (16bit crc follows header)
1 - Not protected
E 4 (15,12) Bitrate index
bits V1,L1 V1,L2 V1,L3 V2,L1 V2,L2 V2,L3
0000 free free free free free free
0001 32 32 32 32 32 8 (8)
0010 64 48 40 64 48 16 (16)
0011 96 56 48 96 56 24 (24)
0100 128 64 56 128 64 32 (32)
0101 160 80 64 160 80 64 (40)
0110 192 96 80 192 96 80 (48)
0111 224 112 96 224 112 56 (56)
1000 256 128 112 256 128 64 (64)
1001 288 160 128 288 160 128 (80)
1010 320 192 160 320 192 160 (96)
1011 352 224 192 352 224 112 (112)
1100 384 256 224 384 256 128 (128)
1101 416 320 256 416 320 256 (144)
1110 448 384 320 448 384 320 (160)
1111 bad bad bad bad bad bad
NOTES: All values are in kbps
V1 - MPEG Version 1 V2 - MPEG Version 2 and Version 2.5
L1 - Layer I L2 - Layer II L3 - Layer III
F 2 (11,10) Sampling rate frequency index (values are in Hz)
bits MPEG1 MPEG2 MPEG2.5
00 44100 22050 11025
01 48000 24000 12000
10 32000 16000 8000
11 reserv. reserv. reserv.
G 1 (9) Padding bit
0 - frame is not padded
1 - frame is padded with one extra bit
H 1 (8) Private bit (unknown purpose)
I 2 (7,6) Channel Mode
00 - Stereo
01 - Joint stereo (Stereo)
10 - Dual channel (Stereo)
11 - Single channel (Mono)
J 2 (5,4) Mode extension (Only if Joint stereo)
value Intensity stereo MS stereo
00 off off
01 on off
10 off on
11 on on
K 1 (3) Copyright
0 - Audio is not copyrighted
1 - Audio is copyrighted
L 1 (2) Original
0 - Copy of original media
1 - Original media
M 2 (1,0) Emphasis
00 - none
01 - 50/15 ms
10 - reserved
11 - CCIT J.17
两个关键变量:
1)每帧的播放时间:有26ms也有52ms的,我用MP3编辑工具看了一些MP3,好像只是跟MPEG Version相关;
我的英文MP3资料都是MPEG Version 2.5,所以我就用52ms了。
2)帧大小:
FrameSize = (((MpegVersion == MPEG1 ? 144 : 72) * Bitrate) / SamplingRate) + PaddingBit
例如:我的资料是 Bitrate = 32000, SamplingRate =11025, and PaddingBit =0
FrameSize = (72* 32000) / 11025 + 0 = 208 bytes
再加上.lrc文件中的时间信息,就可以定位出每个句子在MP3文件中的偏移位置。
接下来就是要找个函数用来转MP3到wav格式了,偶然发现“xxx背单词5白金版”中有个xxx.dll,其中有个导出函数Mp3BuftoWaveBuf,这正是我的所爱。具体怎么为我所用呢?btw:在看雪混了一年多了,搞不定这个,怎么向同志们交代啊?
拿出我的古董OD,也不知道有没有anti功能。下个断点bp Mp3BuftoWaveBuf,运行后点句子朗读立即断下。单步几下来到这里:
0046F210 /$ 8B4424 10 MOV EAX,DWORD PTR SS:[ESP+10]
0046F214 |. 8B4C24 0C MOV ECX,DWORD PTR SS:[ESP+C]
0046F218 |. 8B5424 08 MOV EDX,DWORD PTR SS:[ESP+8]
0046F21C |. 50 PUSH EAX
0046F21D |. 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8]
0046F221 |. 51 PUSH ECX
0046F222 |. 52 PUSH EDX
0046F223 |. 50 PUSH EAX
0046F224 |. E8 AD750600 CALL <JMP.&AudioConvert.#4> //Mp3BuftoWaveBuf
0046F229 |. 83C4 10 ADD ESP,10
0046F22C \. C3 RETN
push了4次,显然有4个参数,到堆栈里看看。原来第一个参数是MP3格式数据的首地址,参数2是MP3数据长度;第三、四个参数都是指针,分别指向转换后wav格式数据的首地址和转换的长度。到这里我们就可以得到每个句子的wav格式数据。
最后就是用wav格式数据发声了。下个断点bp PlaySoundA 断在下面:
00404CC5 |. 8B15 CCB65500 MOV EDX,DWORD PTR DS:[55B6CC] //edx指向我们的wav格式数据,前面的大量工作就是干这个的
00404CCB |. 6A 04 PUSH 4
00404CCD |. 6A 00 PUSH 0
00404CCF |. 52 PUSH EDX
00404CD0 |. FF15 38075200 CALL DWORD PTR DS:[<&WINMM.PlaySoundA>] ; WINMM.PlaySoundA
单步经过这个call就能听到朗读句子了。调试准备到此为止,该用delphi来实现了。
几处关键的地方如下:
implementation
function Mp3BuftoWaveBuf(mp3Buf :PAnsiChar; mp3len:DWORD;var pWaveBufAddr:PDWORD;var lpwaveLen:PDWORD) : DWORD;stdcall; external 'tianyi_love.dll';
//逆出来的函数声明
procedure MyThreadFunc(Sender: TObject); //朗读线程函数
var
filehandle,maphandle: THandle;
filesize,wavedata,tmp,len: DWORD;
mp3data:PAnsiChar;
pwaveLen:PDWORD;
begin
mp3data:=nil;
maphandle:=0;
filehandle:=0;
try
filehandle := FileOpen(form1.Label1.Caption, fmOpenReadWrite);
filesize := GetFileSize(filehandle, Nil);
maphandle := CreateFileMapping(filehandle, nil, PAGE_READWRITE, 0, filesize, nil);
mp3data:= MapViewOfFile(maphandle, FILE_MAP_ALL_ACCESS, 0, 0, filesize);
tmp:=cardinal(mp3data);
tmp:=tmp+mp3offset[current_line-1];
mp3data:=pchar(tmp);
if current_line=total then len:=filesize-mp3offset[current_line-1]+1
else len:=mp3offset[current_line]-mp3offset[current_line-1]+1;
Mp3BuftoWaveBuf(mp3data,len,pBufAddr,pwaveLen); //对不起,借用一下
asm
ADD ESP,$10
mov eax,pBufAddr
mov wavedata,eax
end;
PlaySoundA(pchar(wavedata),0, 4); //朗读句子
playing:=false;
form1.Label3.Caption:='';
finally
UnmapViewOfFile(mp3data);
CloseHandle(maphandle);
CloseHandle(filehandle);
TerminateThread(hThreadHandle, 0);
end;
end;
procedure TForm1.ListBox1Click(Sender: TObject);
var i,lines:integer;
ss,tmp:string;
mp3off:real;
begin
FillChar(mp3offset,SizeOf(mp3offset),0);
memo2.Clear;
Richedit1.Clear;
tmp:=Listbox1.items[Listbox1.itemindex] ;
ss:=tmp+'.mp3';
Label1.Caption:=current_path+ss;
memo2.Lines.LoadFromFile(current_path+tmp+'.lrc');
total:=memo2.lines.count;
for lines:=0 to memo2.lines.count-1 do
begin
tmp:=memo2.lines[lines];
i:=pos(']',tmp);
ss:=copy(tmp,i+1,length(tmp)-i);
RichEdit1.Lines.Add(ss);
i:=pos(':',tmp);
ss:=copy(tmp,i-2,2);
mp3off:=strtofloat(ss)*60;
i:=pos('.',tmp);
ss:=copy(tmp,i-2,2);
mp3off:= strtofloat(ss)+mp3off;
i:=pos(']',tmp);
ss:=copy(tmp,i-2,2);
mp3off:= strtofloat(ss)/100+mp3off;
mp3off:=mp3off*208/0.052; //此处是根据我的MP3资料计算的两个定位时用到的关键值,加几行代码就智能了
tmp:=floattostr(mp3off);
mp3offset[lines]:=cardinal(strtoint(tmp)); //得到每个句子在MP3中的开始偏移量,存入数组备用
end;
end;
procedure TForm1.RichEdit1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
MemoInfo: TMemoInfo;
i:integer;
begin
if playing then exit;
GetMemoPos(RichEdit1, MemoInfo);
with MemoInfo do
begin
i:=Succ(Line);
if i>total then exit;
current_line:=i; //得到朗读句子的行号
end;
playing:=true;
hThreadHandle:=CreateThread(nil,0,@MyThreadfunc,nil,0,dwThreadID);//生成朗读线程,开始朗读指定句子
if hThreadHandle=0 then showmessage('Didn’t Create a Thread');
Label3.Caption:='Playing...';
end;
By 天易love 2010-02-07
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: