Как нарисовать графическое представление wav-файла?

131
15 декабря 2018, 03:00

Есть считанный с файла WAV_HEADER и data segment. Как построить waveform, т.е график такого вида:

 struct  WAV_HEADER
{
    /* RIFF Chunk Descriptor */
    uint8_t         RIFF[4];        // RIFF Header Magic header
    uint32_t        ChunkSize;      // RIFF Chunk Size
    uint8_t         WAVE[4];        // WAVE Header
                                    /* "fmt" sub-chunk */
    uint8_t         fmt[4];         // FMT header
    uint32_t        Subchunk1Size;  // Size of the fmt chunk
    uint16_t        AudioFormat;    // Audio format 1=PCM,6=mulaw,7=alaw,     257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
    uint16_t        NumOfChan;      // Number of channels 1=Mono 2=Sterio
    uint32_t        SamplesPerSec;  // Sampling Frequency in Hz
    uint32_t        bytesPerSec;    // bytes per second
    uint16_t        blockAlign;     // 2=16-bit mono, 4=16-bit stereo
    uint16_t        bitsPerSample;  // Number of bits per sample
                                    /* "data" sub-chunk */
    uint8_t         Subchunk2ID[4]; // "data"  string
    uint32_t        Subchunk2Size;  // Sampled data length
};
Answer 1

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

Алгоритм выглядит так:

  1. Выделить из WAV-данных массив мгновенных значений звука (семплов) в формате, с которым удобно работать (например, значения float в диапазоне от -1.0 до 1.0). Информацию о том, как это делается, можно найти например здесь.

  2. Разбить массив на интервалы, примерно соответствующие пикселю на изображении графика. Формула для вычисления длины интервала:

interval = (N_SAMPLES) / (K * W_WINDOW);

где

N_SAMPLES - число семплов (одного канала);
W_WINDOW - ширина окна для отображения графика, в пикселах;
K - коэффициент, задающий "детализированность" графика, обычно в диапазоне (1.0; 2.0).

  1. Определить значение модуля амплитуды (среднее, пиковое) на каждом интервале

  2. Нарисовать график с помощью любой графической библиотеки

Пример кода для построения графика средних амплитуд одного канала (т.е., верхней половины приведенного рисунка):

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <memory.h>
#include <math.h>
#include <glut.h>   //Для рисования
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#pragma comment(lib, "glut32.lib")
struct  WAV_HEADER
{
    /* RIFF Chunk Descriptor */
    uint8_t         RIFF[4];        // RIFF Header Magic header
    uint32_t        ChunkSize;      // RIFF Chunk Size
    uint8_t         WAVE[4];        // WAVE Header
                                    /* "fmt" sub-chunk */
    uint8_t         fmt[4];         // FMT header
    uint32_t        Subchunk1Size;  // Size of the fmt chunk
    uint16_t        AudioFormat;    // Audio format 1=PCM,6=mulaw,7=alaw,     257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
    uint16_t        NumOfChan;      // Number of channels 1=Mono 2=Sterio
    uint32_t        SamplesPerSec;  // Sampling Frequency in Hz
    uint32_t        bytesPerSec;    // bytes per second
    uint16_t        blockAlign;     // 2=16-bit mono, 4=16-bit stereo
    uint16_t        bitsPerSample;  // Number of bits per sample
                                    /* "data" sub-chunk */
    uint32_t         Subchunk2ID; // "data"  string
    uint32_t        Subchunk2Size;  // Sampled data length
};

//Возвращает следующий семпл начиная с указанной позиции в массиве байт
float ReadNextSample (        
    /*IN*/ WAV_HEADER* hdr, //заголовок WAV
    /*IN*/ unsigned char* rawdata, //данные WAV
    /*IN,OUT*/ uint64_t * startindex //начальный индекс (после вызова функции устанавливается в индекс следующего семпла)
){
      float res = 0.0f;
      unsigned char byte = 0;
      int16_t val16 = 0;
      switch (hdr->AudioFormat)
      {
        case 1://PCM
        if (hdr->bitsPerSample == 8)
        {      
            byte = rawdata[*startindex];
            *startindex += 1;
            res = (byte - 128.0f) / 255.0f;
         }
         else if (hdr->bitsPerSample == 16)
         {
            memcpy(&val16,rawdata+(*startindex),2);
            *startindex += 2;
            res = (val16) / 32767.0f;
         }         
         if (res > 1.0f) res = 1.0f;
         if (res < -1.0f) res = -1.0f;
         break;
         case 3: //IEEE Float
         if (hdr->bitsPerSample == 32) {
                memcpy(&res,rawdata+(*startindex),sizeof(float));
                        *startindex += sizeof(float);
         }
         break;
      }
      return res;
}
//Преобразует данные WAV в массив нормализованных Float-значений в интервале (-1.0;1.0). Возвращает размер массива.
uint64_t GetSamples(
    /* IN */ WAV_HEADER* hdr, //заголовок WAV
    /* IN */ unsigned char* data, //данные WAV
    /* OUT */ float** psamples //выходной массив
){
    float* samples = NULL;
    uint64_t samples_count = hdr->Subchunk2Size / ((int)hdr->bitsPerSample / 8);
    samples = (float*)malloc(samples_count * sizeof(float));
    uint64_t i_data; 
    uint64_t i_sample=0;    
    for(i_data=0; i_data<hdr->Subchunk2Size; ){     
        if(i_sample>=samples_count)break;
        samples[i_sample] = ReadNextSample(hdr,data,&i_data);       
        i_sample++;
    }
    *psamples = samples;
    return i_sample;
}

//Считывает заголовок и данные WAV из файла
uint64_t ReadWav(
    /* IN */ wchar_t* file, //путь к файлу
    /* OUT */ WAV_HEADER* hdr, //указатель на переменную для записи заголовка
    /* OUT */ unsigned char** pdata //указатель на переменную для записи данных
){
    memset(hdr,0,sizeof(WAV_HEADER));
    FILE* f = _wfopen(file,L"rb");
    if(f == NULL){
        printf("Cannot open file!\n");
        return 0;
    }
    //считываем заголовок
    fread(&(hdr->RIFF),4,1,f);
    fread(&(hdr->ChunkSize),4,1,f);
    fread(&(hdr->WAVE),4,1,f);
    fread(&(hdr->fmt),4,1,f);
    fread(&(hdr->Subchunk1Size),4,1,f);
    if(!(hdr->RIFF[0] == 'R' && hdr->RIFF[1] == 'I' && hdr->RIFF[2] == 'F' && hdr->RIFF[3] == 'F') ||
       !(hdr->WAVE[0] == 'W' && hdr->WAVE[1] == 'A' &&  hdr->WAVE[2] == 'V' &&  hdr->WAVE[3] == 'E')){
           printf("File is not RIFF/WAV!\n");
           fclose(f);
           return 0;
    }
    fread(&(hdr->AudioFormat),2,1,f);
    fread(&(hdr->NumOfChan),2,1,f);
    fread(&(hdr->SamplesPerSec),4,1,f);
    fread(&(hdr->bytesPerSec),4,1,f);
    fread(&(hdr->blockAlign),2,1,f);
    fread(&(hdr->bitsPerSample),2,1,f);
    uint16_t fmtExtraSize = 0;  
    if (hdr->Subchunk1Size == 18) {  
        fread(&(fmtExtraSize),2,1,f);                        
        fseek(f,fmtExtraSize,SEEK_CUR);
    }         
    unsigned char* data=NULL;
    size_t data_size;
    size_t res; 
    //пытаемся считать данные
    fread(&(hdr->Subchunk2ID),4,1,f);
    fread(&(hdr->Subchunk2Size),4,1,f);
    while(1){
      data = (unsigned char*)malloc(hdr->Subchunk2Size);
      data_size = fread(data,1,hdr->Subchunk2Size,f);     
      if(hdr->Subchunk2ID == 0x61746164) break;//данные найдены
      //если Subchunk2Id нет тот, что ожидался, пропускаем и пробуем снова
      free(data);
      data = NULL;
      hdr->Subchunk2ID = 0;
      hdr->Subchunk2Size = 0;
      res=fread(&(hdr->Subchunk2ID),4,1,f);
      if(res < 4)break;
      res=fread(&(hdr->Subchunk2Size),4,1,f);
      if(res < 4)break;
    }
    fclose(f);
    *pdata = data;
    if(data == NULL || hdr->Subchunk2Size == 0) return 0;
    if(data_size < hdr->Subchunk2Size) {
        printf("Warning: data size is lower then expected!\n");
        hdr->Subchunk2Size = res;
    }
    return hdr->Subchunk2Size;
}
float* values = NULL; //массив значений Y для графика
uint64_t interval=0; //размер интервала усреднения
uint64_t intervals = 0; //количество интервалов
int width = 500; //ширина окна
void Initialize()
{
 //Выбрать фоновый цвет
 glClearColor(1.0,1.0,1.0,1.0);
 //Установить проекцию
 glMatrixMode(GL_PROJECTION);
 glLoadIdentity();
 glOrtho(0.0,1.0,0.0,1.0,-1.0,1.0);
}
void DrawGraph(float* values,uint64_t count) //отрисовка графика по массиву значений
{ 
 glColor3f(0.0,1.0,0.0); //Выбираем цвет
 glBegin(GL_LINES);
 float x0,y0;
 float x,y;
 //Задаем точки  
 for(int i=0;i<intervals;i++){
    x0=i * 1.0f/(intervals);
    y0=values[i];
    glVertex3f(x0,y0,0.0); 
    x=(i+1) * 1.0f/(intervals);
    y=values[i+1];
    glVertex3f(x,y,0.0);    
 }
 glEnd();
 glFlush();
}
void Draw(){
    DrawGraph(values,intervals);
}
int main(int argc, char **argv)
{
    WAV_HEADER hdr={0}; //заголовок WAV 
    unsigned char* bytes = NULL; //данные WAV   
    float* data = NULL; //массив семплов
    uint64_t count = 0; //число семплов
    //считываем файл
    if(ReadWav(L"c:\\Media\\File1.wav",&hdr,&bytes)==0){
        getchar();
        return 0;
    }   
    //получаем массив семплов
    count = GetSamples(&hdr,bytes,&data);
    free(bytes);    
    //определяем интервал для усреднения
    int chans = hdr.NumOfChan;
    interval = (count/ chans) / (1.5f* width);
    if(interval==0) interval=1;
    intervals = (count/ chans) / interval;
    //формируем массив значений для графика
    values = (float*)malloc(intervals*sizeof(float));   
    float y;
    for(int i=0;i<intervals;i++){
        y=0.0f;
        for(int j=i*interval;j<(i+1)*interval;j++){
            if(j*chans>=count)break;            
            y += abs(data[j * chans]);
        }
        y =   y/(float)interval; //находим среднее значение для каждого интервала
        values[i]=y;        
    }
    free(data); 
    glutInit(&argc,argv);
    glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);
    glutInitWindowSize(width,400);      //Размер окна
    glutInitWindowPosition(100,100);    //Позиция окна
    glutCreateWindow("Graph");      
    Initialize();                       
    glutDisplayFunc(Draw);              //Задаем функцию отрисовки
    glutMainLoop(); 
    return 0;
}

Поддерживает форматы кодирования PCM (8 или 16 бит) и IEEE Float (32 бит). Требует Visual C++ 2010+ и библиотеку Glut.

Источники:

Audacity Waveform

Algorithm to draw waveform from audio

Drawing Waveforms

READ ALSO
Преобразование символов cp1251 -&gt; utf-8 -&gt; cp1251

Преобразование символов cp1251 -> utf-8 -> cp1251

Работаю под MS VS 2017, VC++ + boost, Win-7Задача такая: читаем из консоли (или другого места с настройкой кодировки cp1251), далее надо преобразовать в UTF-8,...

118
Какой метод будет вызван?

Какой метод будет вызван?

Вопрос был озвучен на собеседовании:

147
Android Camera не выводит в image view

Android Camera не выводит в image view

Мое приложение должно фотографировать и выводить на экранСделал все как в примере здесь

108
Как убрать отступы от краев экрана кустарного UI в зависимости от разрешения?

Как убрать отступы от краев экрана кустарного UI в зависимости от разрешения?

Я пишу игру на Java, (с использованием движка Slick2D и библиотеки MarteEngine, если это важно), и реализуя свой UI столкнулся с появлением отступов слева...

131