Загрузка и воспроизведение несжатого wav-файла

Главная Форумы Программирование Программирование на С++ Загрузка и воспроизведение несжатого wav-файла

Помечено: , ,

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 1 неделя назад.

  • Автор
    Сообщения
  • #4181

    Я не являюсь автором заметки (нашел ее у себя на компьютере, разбирая завалы). Материал полезный, но найти автора не получилось (как и найти этот же материал в интернете). Публикую, надеюсь автор не будет против (не для того ведь он писал это сокровище, чтобы оно пылилось только на моем жестком диске?).

    1. Введение

    В этой статье я расскажу о том, как загружать и проигрывать wav-файл, проигрывать будем при помощи DirectSound, поэтому, если у Вас не стоит DirectX SDK, то идите на microsoft.com, и скачайте там 9-ую версию. Писаться всё это дело будет под C++, примеры тестировались в MSDEV2005.

    2. Структура wav – файла

    Для начала рассмотрим структуру, wav-файла, я не раскрою Вам эту тайну полностью, но то о чём говорил в заголовке статьи, наверное, сделаю.

    Wav-файл состоит из нескольких блоков, которые содержат информацию, причём вовсе не обязательно, что все эти блоки идут в определённом порядке.

    Вот некоторые из этих блоков:

    2.1. Блок RIFF

    Тип Значение Комментарий
    char[4] MAKEFOURCC(‘R’,’I’,’F’,’F’); Идентификатор RIFF, который присущ всем RIFF-файлам, wav – не исключение.
    long Длина файла – 8 Длина без этого заголовка

    2.2. Блок WAVE

    Тип Значение Комментарий
    char[4] MAKEFOURCC(‘W’,’A’,’V’,’E’); Идентификатор WAVE, есть у всех wav-файлов
    char[4] MAKEFOURCC(‘f’,’m’,’t’,’ ’); Идентификатор fmt
    long Обычно 16. (См. прим. ниже) Длина описания данных
    WAVEFORMATEX В зависимости от файла Описание аудио-данных

    2.3. Блок FACT(необязательный)

    Тип Значение Комментарий
    char[4] MAKEFOURCC(‘f’,’a’,’c’,’t’); Идентификатор ‘fact’, который говорит, что дальше идёт именно этот блок.
    long Длина блока ‘fact’ Количество байт до следующего блока
    ??? ??? Что тут находится, я не знаю, но это
    не важно, ходят слухи, что тут хранится количество семплов (???) в
    файле, и то что он используется исключительно для сжатых wav-файлов…

    2.4. Блок DATA:

    Тип Значение Комментарий
    char[4] MAKEFOURCC(‘d’,’a’,’t’,’a’); Информирование о блоке ‘data’
    long Длина звуковых данных Количество байт до следующего блока
      Аудиоданные То, ради чего мы всё это прокопали =)


    Остальные блоки, такие, как комментарий, название исполнителя и т.д. нас не интересуют

    3. Алгоритм

    Вообще, мы не учтём то, что все блоки стоят не в нормальном порядке. Почему? А потому что стандартная виндовская программа «Звукозапись», читает любые нормальные wav-файлы, а пишет, нам нужные, а мы ведь пишем игру, а не плеер ;)?

    Итак, сначала при откроем файл, потом прочитаем первые четыре байта, проверим, что они равны MAKEFOURCC(‘R’,’I’,’F’,’F’), или, если вы считывали в строку, то просто “RIFF”, и если проверка провалилась, то бьём тревогу, мол, это не wav-файл.

    Далее пропускаем информацию о размере, и читаем ‘WAVE’-блок, так же проверяем, иначе бьём тревогу, далее идёт блок ‘fmt ‘, считываем ещё 4 байта и проверяем их, заметьте, что все буквы в блоке ‘fmt ‘ маленькие!!!

    Дальше считываем длину блока ‘fmt ‘, нам от него нужна только структура WAVEFORMATEX, которую, нам нужно передать DirectSound. Её размер = 16 байт, а на всё остальное мы забиваем…

    Кстати, в структуре WAVEFORMATEX, есть поле под названием “wFormatTag”, если оно не равно 1, значит данные либо запакованные, либо не нашего формата, короче вывод состоит в том, что прочитать мы его не сможем… Из этого следует, что лучше в этом случае считывание прекратить и выдать ошибку.

    Дальше самое интересное при считывании этого вида файла. Когда я искал информацию по данному поводу, я зашёл на форум, задал вопрос, через месяц (!!!) мне ответили и дали пример, я разобрался, и теперь пишу эту статью, но не один (!!!) пример не был рассчитан на блок ‘fact’, что он делает полезного, я так и не понял, но я на него просто
    забиваю, т.к. после наименования блока, обычно идёт его размер, так я и сделал, и теперь всё работает…

    А теперь по делу, читаем 4-е байта, проверяем, если в них записано “fact”, то считываем 4-е байта, количество байтов, которое мы должны пропустить будет считанным числом. Тем самым мы добираемся до секции ‘data’.

    Может быть такая ситуация, что Вы не найдёте блока ‘fact’, а найдёте сразу ‘data’, не пугайтесь в любом случае всё будет работать нормально.

    Итак, обработка блока ‘data’, как обычно считываем имя блока (эти самые 4-е байта), если оно неравно ‘data’, то бунтуем, потом считываем размер данных, заметьте, что этот размер теперь для нас важен!!! Т.к. это тот размер, который обозначает размер аудио-данных!!!

    Считали, а за ним, та самая вкусность ради которой мы всё это проделали =). Ням-ням, считываем, дальше всё раскидываем по буферам, и вуаля! Усе готово!!!

    Теперь переходим непосредственно к коду.

    4. Программная часть

    Создаём консольное приложение, пустое, никаких сервисов, типа MFC не используем.

    Сначала о заголовках, надо подключить сам DirectSound, а так же всё стандартное для консольного приложения считывающего из файла, т.е. потоки ввода-вывода и поток чтения из файла, ну и, разумеется windows, что бы узнать окно, к которому привязывать DirectSound:

    #include        <windows.h>
    #include        <dsound.h>
    #include        <iostream>
    #include        <fstream> 

    Не забываем подключить необходимые lib’ы:
    #pragma comment(lib,"dsound.lib")
    #pragma comment(lib,"dxguid.lib") 

    Отключаем предупреждение об устаревшей функции…
    #pragma warning(disable:4996) //ОТключаем предупреждение strcat
    Используем пространство имен std:
    using namespace std;
    DirectSound у нас будет общим, поэтому мы его объявляем в глобальных переменных:
    IDirectSound8 *g_pDirectSound;
    Прототип функции, которая будет проигрывать файл:
    void PlayWav(const char* szFileName);
    Наша главная функция :)
    int main()
    {
            DirectSoundCreate8(0,&g_pDirectSound,0);                               
            g_pDirectSound->SetCooperativeLevel(GetForegroundWindow(),DSSCL_NORMAL);
    
            PlayWav("chord.wav");                           
           
            g_pDirectSound->Release();                                               
            g_pDirectSound=NULL;
            return 0;
    }

    В ней мы инициализируем DirectSound и проигрываем файл.

    Теперь, непосредственно насчёт функции PlayWav:

    void    PlayWav(const char* szName)
    {
            char    szFileName[255];                       
            memset((void*)szFileName,0,255);                               
    
            GetWindowsDirectoryA((char*)szFileName,255);    
            strcat((char*)szFileName,"\Media\");
            strcat((char*)szFileName,szName);

    Все файлы, которые нам указали лежат в папке «WINDOWSMedia», поэтому мы прицепляем это к каждому имени файла, мы ведь не знаем, на какой диск у пользователя установлен WINDOWS?
     cout<<"Loading file: "<<szFileName<<endl;              
            ifstream        fIn;                               
            fIn.open((char*)szFileName,ios::beg|ios::in|ios::binary)
    if(!fIn)
            {
                    cout<<"File not find!"<<endl;
                    return;
            }

    Проверяем файл на наличие, открываем, если такой имеется.
            cout<<"Finding chunk "RIFF""<<endl;
            DWORD   dwRIFF=0;   
            fIn.read((char*)&dwRIFF,4);
            if(dwRIFF!=MAKEFOURCC('R','I','F','F'))
            {
                    cout<<"Can not find chunk "RIFF""<<endl;
                    cout<<"tUnsupported file format!"<<endl;
                    return;
            };
            cout<<"OK!"<<endl;
            fIn.ignore(4);     
    
            DWORD       dwWAVE=0;
            DWORD       dwFormat=0;
            long        lSizeFmt=0;
    
            cout<<"Finding chunk "WAVE""<<endl;
            fIn.read((char*)&dwWAVE,4);
            if(dwWAVE!=MAKEFOURCC('W','A','V','E')) 
            {
                    cout<<"Can not find chunk "WAVE""<<endl;
                    cout<<"tUnsupported file format!"<<endl;
                    return;
            };
            cout<<"OK!"<<endl;
    
            cout<<"Finding chunk "fmt ""<<endl;
            fIn.read((char*)&dwFormat,4);
            if(dwFormat!=MAKEFOURCC('f','m','t',' '))              
            {
                    cout<<"Can not find chunk "fmt ""<<endl;
                    cout<<"tUnsupported file format!"<<endl;
                    return;
            };
            cout<<"OK!"<<endl;
    
            cout<<"Finding information about audio..."<<endl;
            fIn.read((char*)&lSizeFmt,4);
            if(lSizeFmt==0)
            {
                    cout<<"Can not find size of audio-information"<<endl;
                    cout<<"tUnsupported file format!"<<endl;
                    return;
            };
            cout<<"Size of information:"<<lSizeFmt<<endl;
            WAVEFORMATEX    waveFormat=WAVEFORMATEX();
            fIn.read((char*)&waveFormat,16);
            fIn.ignore(lSizeFmt-16);

    Прыгаем по блокам, кстати взгляните на последнюю строку, хоть размер структуры WAVEFPRMATEX==16, мы всё равно игнорируем байты, которые идут после описания формата, потому что ещё при различной кодировке файла, может быть добавлена дополнительная информация.

    Далее проверяем сжат-ли файл, если сжат, то мы его прочесть не сможем…

    if(waveFormat.wFormatTag!=1)
            {
                    cout<<"This file is compressed!"<<endl;
                    cout<<"tUnsupported file format!"<<endl;
                    return;
            }

    Дальше идёт переломный момент с блоком ‘fact’, я это уже объяснял в теоретической части, теперь можно взглянуть и на код, возможно, будет более понятнее =)
            DWORD   dwNextChunk=0;
            fIn.read((char*)&dwNextChunk,4);
            if(dwNextChunk==MAKEFOURCC('f','a','c','t'))           
    {
                    cout<<"Finded chunk "fact"!"<<endl;      
                    DWORD   dwSizeFact=0;                                   
                    fIn.read((char*)&dwSizeFact,4);    
                    fIn.ignore(dwSizeFact);      
                    fIn.read((char*)&dwNextChunk,4);                               
            };                                                                  
            cout<<"Finding chunk "data""<<endl;
            if(dwNextChunk!=MAKEFOURCC('d','a','t','a'))           
    {
                    cout<<"Can not find chunk "data"!"<<endl;
                    cout<<"tUnsupported file format!"<<endl;
                    return;
            };                 
            cout<<"OK"<<endl;

    Я думаю, что этот код понятен, мы просто проверяем стоит-ли перед блоком ‘data’, не нужный нам блок ‘fact’, и пропускаем его в случае нахождения.

    Дальше непосредственно узнаём размер аудио-данных, выделяем для них память, и считываем, не забывая закрыть файл.

            DWORD   dwDataSize=0;                                    fIn.read((char*)&dwDataSize,4);                     
            char    *pData=new char[dwDataSize];       
            fIn.read(pData,dwDataSize);                    
            cout<<"Closing file, Loading melody!"<<endl;
            fIn.close();

    Теперь идёт работа с DirectSound, создаём буфер…

            IDirectSoundBuffer  *pSoundBuffer=NULL;
            DSBUFFERDESC        dsbd=DSBUFFERDESC();
            dsbd.dwBufferBytes  =dwDataSize;
            dsbd.dwFlags        =DSBCAPS_STATIC;
            dsbd.dwSize         =sizeof(DSBUFFERDESC);
            dsbd.lpwfxFormat    =&waveFormat;
    
            g_pDirectSound->CreateSoundBuffer(&dsbd,&pSoundBuffer,0);

    Заполняем…
            void            *pDst=0;
            DWORD           dwSize=0;
            pSoundBuffer->Lock(0,0,&pDst,&dwSize,0,0,DSBLOCK_ENTIREBUFFER);
            memcpy(pDst,pData,dwSize);
            pSoundBuffer->Unlock(pDst,dwSize,0,0);
            pSoundBuffer->Play(0,0,0);
    
            DWORD   hrStatus=0;

    Задерживаем окончание выполнения функции до тех пор, пока проигрывается файл:
            do
            {
                    pSoundBuffer->GetStatus(&hrStatus);
            }
            while(hrStatus==DSBSTATUS_PLAYING);
    return;
    };

    Вот и всё!!!

Для ответа в этой теме необходимо авторизоваться.