В этом уроке мы будем читать блоками данные из звукового «.wav» файла одновременно записывая блоки данных в результирующий файл.

Для того чтобы проводить эксперименты с обработкой звука, нам как минимум нужно откуда-то получать такие данные. Один из способов – это обработка звуковых данных, которые находятся в звуковом файле. В нашем (конкретном) случае мы будем работать с таким распространенным звуковым форматом как «RIFF» который многим более известен по возможному расширению файлов «.wav». Забегая наперед, можно предположить, что придется обрабатывать файлы больших размеров. Это должно происходить следующим образом:

  • получить блок данных определенного размера из начального файла;
  • обработать данный блок данных и сохранить результат в результирующий файл;
  • получить следующий блок данных из начального файла;
  • обработать и записать в результирующий файл… и т.д.

Для задач блочной обработки звуковых файлов нам вполне подходит замечательная библиотека «libsndfile», которая распространяется по условиям лицензии «LGPL». Данная библиотека является библиотекой с открытым исходным кодом а также является мультиплатформенной. При желании можно изменить ее под свои потребности или же просто посмотреть как другие разработчики пишут работающий и полезный код (что тоже немаловажно).

Для начала Вам придется скачать с официального сайта файл libsndfile-1.0.25-w32-setup.exe (версия файла может меняться со временем, а ссылка и вовсе перестать функционировать). После этого запустите и установите данную библиотеку. Процесс установки представляет собой извлечение файлов в указанную директорию. После завершения установки можно проверить указанную для извлечения директорию (конкретно в моем случае это директория «C:\Program Files\Mega-Nerd\libsndfile\») и обнаружить следующее:

  • папка «bin», которая содержит основной файл интересующей нас библиотеки «libsndfile-1.dll»;
  • папка «lib», которая содержит файл, необходимый для статической линковки «libsndfile-1.lib»;
  • папка «include» содержит заголовочный файл «sndfile.h» в котором указаны функции для работы с данной библиотекой.

Итого, для работы с библиотекой нам понадобятся файлы:

  • «libsndfile-1.dll»;
  • «libsndfile-1.lib»;
  • «sndfile.h».

Ну вот, мы готовы к созданию простенького проекта для осуществления чтения-записи звуковых выборок. Проект будем создавать в среде разработки «VisualStudio 2005″.

создаем проект Win32 (File->New->Project…)

 

creating project

 

— Жмем Ok->Finish. Проект создан.

В результате проделанных операций имеем директорию с файлами проекта. Сейчас нам необходимо добавить к данному проекту файлы библиотеки «libsndfile». Для этого нужно скопировать файлы:

  • «libsndfile-1.dll»;
  • «libsndfile-1.lib»;
  • «sndfile.h».

в папку с только что созданным проектом. В результате у Вас в папке с проектом должны находится файлы:

  • ReadWriteWav.cpp
  • stdafx.cpp
  • libsndfile-1.dll
  • sndfile.h
  • stdafx.h
  • libsndfile-1.lib
  • ReadWriteWav.sln
  • ReadMe.txt
  • ReadWriteWav.vcproj

Настало время подключить библиотеку к проекту. Для этого нужно зайти в настройки проекта, а именно линкера:

 

linker settings

 

Теперь заходим в настройки проекта Properties->Add->Existing Item… Указываем хидер файл «sndfile.h», которая находится в директории проекта.

В файл ReadWriteWav.cpp дописываем строку:

 

#include "sndfile.h"

 

На данном этапе мы имеем начальный проект для работы с библиотекой «libsndfile» т.е. в главной функции _tmain(…) стало возможным вызывать функции библиотеки. Проверим, все ли нормально с подключенной библиотекой. Для этого наш главный файл должен содержать следующий код:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "stdafx.h"
#include "sndfile.h"
#include <iostream>
 
int _tmain(int argc, _TCHAR* argv[])
{
      char buf[1024] ;
      sf_command(NULL, SFC_GET_LIB_VERSION, buf, sizeof(buf));
      puts (buf) ;
      system("pause");
 
      return 0;
}

 

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

 

console output

 

Ну вот и замечательно! Тогда приступим к написанию маленькой программы чтения-записи звукового «.wav» файла. Текст программы выглядит следующим образом:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// ReadWriteWav.cpp.
//
#include "stdafx.h"
#include 
#include 
// this is made to support WinUnicode
#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
#include "sndfile.h"
 
#define BLOCK_SIZE 512
 
// main sound processing function
static void read_write(SNDFILE *infile, SNDFILE *outfile, 
SF_INFO info)
{
    int     channels = info.channels;
    float   *buf = new float[channels*BLOCK_SIZE];
    float   *bufTmpChan = new float[channels*BLOCK_SIZE];
    int     k, m, readcount, writecount;
 
    while(readcount = static_cast(
    sf_readf_float(infile, buf, BLOCK_SIZE))) > 0)
    {
        // for each of channel
        for (m = 0; m < channels; m++)
        {
            // for each of sample
            // for each of channel
            for (k = 0 ; k < readcount ; k++)
                bufTmpChan[k] = buf[k*channels + m];
 
            // processing data from single channel could 
            // be made here!!!
            // push back processed data into original
            for (k = 0 ; k < readcount ; k++)
                buf[k*channels + m] = bufTmpChan[k];
        }
 
        writecount = static_cast(
        sf_writef_float(outfile, buf, readcount));
    }
 
    delete [] buf;
    delete [] bufTmpChan;
 
    return;
}
 
// entry point of the programs
int _tmain(int argc, _TCHAR* argv[])
{
    _TCHAR  *infilename, *outfilename;
    SNDFILE *infile = NULL;
    SNDFILE *outfile = NULL;
    SF_INFO sfinfo;
 
    infilename = L"In.wav";//argv[1];
    outfilename = L"Out.wav";//argv[2];
 
    // Open the input file
    if ((infile = sf_wchar_open(infilename, SFM_READ, 
    &sfinfo)) == NULL)
    {
        printf("Not able to open file %s.\n", infilename);
        puts(sf_strerror(NULL));
 
        return 1;
    }
 
    // Open/create the output file
    if ((outfile = sf_wchar_open(outfilename, SFM_WRITE, 
    &sfinfo)) == NULL)
    {
        printf("Error open/create output file %s : %s\n", 
        outfilename, sf_strerror(NULL));
 
        return 1;
    }
 
    // main sound processing function
    read_write(infile, outfile, sfinfo);
 
    // closing input and output files
    sf_close(infile);
    sf_close(outfile);
 
    return 0;
}

 

Вкратце объясню данный код. Точкой входа программы является функция _tmain(). Внутри этой функции происходит открытие существующего звукового файла в режиме чтения:

 

if ((infile = sf_wchar_open(infilename, SFM_READ, &sfinfo))
== NULL)

 

тут, sfinfo – структура, в которую записывается информация о звуковом файле, формате звуковых данных и прочее в случае удачного открытия файла. Тут, функция sf_wchar_open возвращает указатель на структуру, которая хранит данные звукового файла (для простоты понимания – это что-то вроде хендла).

Строка:

 

if ((outfile = sf_wchar_open(outfilename, SFM_WRITE, &sfinfo)) 
== NULL)

 

открывает или создает (в случае отсутствующего файла) звуковой файл в режиме записи. Параметры и возвращаемые значения функций подобны тем, которые были рассмотрены при открытии файла на чтение.

Строка:

 

read_write(infile, outfile, sfinfo);

 

вызывает функцию, которая как раз и делает все необходимое (заявленное в требованиях к программе, т.е. читает и записывает блоками звуковые данные). Что очень радует, так это то, что задумываться о типе данных нам не приходится, так как какими не были бы данные, они читаются в тип данных float (в нашем случае). Благодаря моим стараниям нам не стоит особо задумываться и о количестве каналов во входном звуковом файле. В месте:

 

// processing data from single channel could be made here!!!

 

можно вообще ни о чем не задумываться. Просто нужно знать, что в данном месте мы уже имеем блок данных (семплов) для конкретного канала m , а вот этот bufTmpChan массив мы можем свободно брать и обрабатывать как нам того захочется.

Ну вот собственно и все! Считаю, что выполнил свою задачу касательно данного задания. К данной статье я конечно же прикрепляю архив проекта. Ну и в добавку готовый и рабочий проект скачивайте отсюда ReadWriteWav. Удачи!