首页
社区
课程
招聘
[原创]英语点读机的详细制作教程(含源码)
发表于: 2010-2-7 14:09 11811

[原创]英语点读机的详细制作教程(含源码)

2010-2-7 14:09
11811

自制英语学习机

      试了很多软件,感觉“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直播授课

上传的附件:
收藏
免费 7
支持
分享
最新回复 (7)
雪    币: 1491
活跃值: (985)
能力值: (RANK:860 )
在线值:
发帖
回帖
粉丝
2
支持,相当牛逼
哇哈哈
2010-2-7 15:47
0
雪    币: 433
活跃值: (1870)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
3
标题漏掉了!
2010-2-7 16:11
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
有惨好价值  谢谢
2010-2-9 12:08
0
雪    币: 2015
活跃值: (902)
能力值: ( LV12,RANK:1000 )
在线值:
发帖
回帖
粉丝
5
我只是把自己的想法变成了现实,没见过前辈讲过如何借用别人的模块,我自己瞎琢磨的一点心得。MP3的操作也没有用控件,自己计算定位。
2010-2-9 13:18
0
雪    币: 433
活跃值: (1870)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
6
附件里面没看到源码
2010-2-9 13:33
0
雪    币: 2015
活跃值: (902)
能力值: ( LV12,RANK:1000 )
在线值:
发帖
回帖
粉丝
7
节约流量,给了unit1.pas。懂点delphi的就能看懂。
2010-2-9 16:49
0
雪    币: 386
活跃值: (46)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
8
我以为有电路图呢,呵呵!
2010-2-9 17:07
0
游客
登录 | 注册 方可回帖
返回
//