flac的内嵌cue提取

flac的内嵌cue实际上有两个存放位置。按照flac官方的技术文档,flac的文件结构中METADATA_BLOCK可以存放CUESHEET类型的数据,不过这种形式的内嵌cue相当复杂,余抛弃实现提取这种cue了。另外METADATA_BLOCK还可以存放VORBIS_COMMENT类型的数据,数据部中“cuesheet=”后面的即是cue,foobar2000调用flac.exe进行编码并写入内嵌cue时,是写到VORBIS_COMMENT中。存放到VORBIS_COMMENT中似乎更普遍一点。

如果不按照flac的文件结构,用稍微暴力一点的方法提取,也是可以的,这时就和提取tak的内嵌cue非常类似了,只不过flac的位于文件头部。

简单说一下flac的METADATA_BLOCK的结构。一个METADATA_BLOCK由两部分组成,METADATA_BLOCK_HEADER和METADATA_BLOCK_DATA。METADATA_BLOCK_HEADER为固定的四字节(32bit),第一个bit为判断是否是最后一个METADATA_BLOCK,第二至第八bit为METADATA_BLOCK的类型,VORBIS_COMMENT的类型为4,后面的3个字节是METADATA_BLOCK_DATA的长度。

因为只注重内嵌cue,所以只有遇到VORBIS_COMMENT类型,才开始进行cuesheet的捕捉,否则一直跳过。跳过时文件指针移动的偏移量即是METADATA_BLOCK_DATA的长度。

代码:

    CFile OpenFile;
    if (!OpenFile.Open(FlacPathName,CFile::modeRead|CFile::shareDenyWrite|CFile::typeBinary))
    {
        OpenFile.Close();
        ::AfxMessageBox(_T("打开失败!"),MB_OK);
        return FALSE;
    }

    if (OpenFile.GetLength()<1048576) // 小于1M,文档太小了
    {
        OpenFile.Close();
        return FALSE;
    }

    unsigned char Header[5];
    memset(Header,0,5);
    ULONGLONG position=0;
    //4个字节的头部
    OpenFile.Read((void*)Header,4);
    if (strcmp((char*)Header,"fLaC")!=0)
    {
        AfxMessageBox(_T("Not real flac file!"));
        return FALSE;
    }

    unsigned char chr;
    unsigned char *Buffer=NULL;
    UINT Length;
    //4个字节的METADATA_BLOCK_HEADER
    do
    {
        OpenFile.Read((void*)Header,4);
        //解析
        memcpy(&chr,Header,1);
        //检查最高位是否为1
        if ((chr&0x80)==0x80)
        {
            //最后一个METADATA_BLOCK
            if ((chr&0x7F)==0x04)//是VORBIS_COMMENT
            {
                //读取BLOCK长度
                Length=Header[1]*0x10000+Header[2]*0x100+Header[3];
                //申请空间
                Buffer=new unsigned char[Length+1];
                //读取BLOCK DATA
                OpenFile.Read((void*)Buffer,Length);
                Buffer[Length]='\0';
            }
            break;
        }
        else
        {
            //不是最后一个METADATA_BLOCK
            if ((chr&0x7F)==0x04)//是VORBIS_COMMENT
            {
                //读取BLOCK长度
                Length=Header[1]*0x10000+Header[2]*0x100+Header[3];
                //申请空间
                Buffer=new unsigned char[Length+1];
                //读取BLOCK DATA
                OpenFile.Read((void*)Buffer,Length);
                Buffer[Length]='\0';
                break;
            }
            else //不是VORBIS_COMMENT
            {
                //读取BLOCK长度
                Length=Header[1]*0x10000+Header[2]*0x100+Header[3];
                //移动文件指针
                OpenFile.Seek(Length,CFile::current);
                position=OpenFile.GetPosition();
            }
        }
    } while(position<=1048576);

    OpenFile.Close();
    if (!Buffer)
        return FALSE;

    //查找 Cuesheet 标记,自动机模型,大小写不敏感
    int state=0,BeginPos=0,EndPos=0;
    for (UINT i=0;i<Length;++i)
    {
        if ((Buffer[i]>=0x41)&&(Buffer[i]<=0x5A))
            Buffer[i]=Buffer[i]+0x20;

        switch (Buffer[i])
        {
        case 'c':
            state=1;      //C
            break;
        case 'u':
            if (state==1)
                state=2;  //Cu
            else
                state=0;
            break;
        case 'e':
            switch (state)
            {
            case 2:
                state=3;  //Cue
                break;
            case 5:
                state=6;  //Cueshe
                break;
            case 6:
                state=7;  //Cueshee
                break;
            default:
                state=0;
            }
            break;
        case 's':
            if (state==3)
                state=4;  //Cues
            else
                state=0;
            break;
        case 'h':
            if (state==4)
                state=5;  //Cuesh
            else
                state=0;
            break;
        case 't':
            if (state==7)
            {
                state=8;  //Cuesheet
            }
            else
                state=0;
            break;
        default:
            state=0;
        }
        if (state==8)
        {
            BeginPos=i+2;
            break;
        }
    }
    if (BeginPos==0)
    {
        delete []Buffer;
        return FALSE;
    }
    //查找终止符 0D 0A ? 00 00 00(连续3个终止符以上)
    state=0;
    for (int i=BeginPos;i<20480;++i)
    {
        switch (Buffer[i])
        {
        case '\0':
            state++;
            break;
        default:
            state=0;
        }
        if (state==3)
        {
            EndPos=i-3; //指向0D 0A后的第一个字符
            break;
        }
    }

    if (EndPos<=1)
    {
        delete []Buffer;
        return FALSE;
    }

    if ((Buffer[EndPos-2]=='\x0D')&&(Buffer[EndPos-1]=='\x0A'))
        EndPos--;

    int CueLength=EndPos-BeginPos+1;
    if (CueLength<=10) //too short
    {
        delete []Buffer;
        return FALSE;
    }

    unsigned char *CueString;
    CueString=new unsigned char[CueLength+1];
    memcpy(CueString,Buffer+BeginPos,CueLength);
    CueString[CueLength]='\0';

    delete []CueString;
    delete []Buffer;
    return TRUE;

CueString中存放的即是utf-8编码的cue字符串,写入到文件即可。
捕捉cuesheet标记的方法和tak一样的。

2010年9月9日 | 归档于 技术, 程序
标签: , , , , ,
本文目前尚无任何评论.

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: