멀티미디어 파일들을 읽기 위해서는 대부분 복잡한 포맷형식 때문에 이미 만들어진 라이브러리들을 통하여 읽는 것이 일반적이다. 하지만 PCM형식의 WAV파일은 비교적 간단하기 때문에 손쉽게 읽을 수 있을 것이라 생각된다.

WAV파일의 DFT변환을 위해 WAV파일의 저장된 내용을 읽을 필요가 있었는데.. 실제 음파의 값들을 읽을수 있는 라이브러리를 찾다가 직접 WAV파일을 읽는 프로그램을 작성하더라도 큰 어려움이 없을 것같아 만들어 보았다.

우선  PCM WAV파일의 포맷부터 보자..

 

이름

형식

Chunk Head

Chunk ID

4 Bytes ASCII String

“RIFF” - MS에서 사용하는 멀티미디어를 저장하는 포맷으로 항상 RIFF라 생각하면 됩니다.

Chunk Size

4 Bytes Little Endian

전체파일의 총 길입니다.

Format

4 Bytes ASCII String

“WAVE” - WAV파일일 경우

Sub Chunk 1

Sub Chunk 1 ID

4 Bytes ASCII String

“fmt “ - WAV파일일 경우

Sub Chunk 1 Size

4 Bytes Little Endian

Sub Chunk1의 길이

Audio Format

2 Bytes Little Endian

1 – PCM

Number of Channels

2 Bytes Little Endian

1 – Mono

2 – Stereo

Sample Rate

4 Bytes Little Endian

8000, 22000, 44000…… 등등

Byte Rate

4 Bytes Little Endian

(Sample Rate) *  (Number of Channels) * (Bit Per Samples) / 8

Block Align

2 Bytes Little Endian

(Number of Channels) * (Bit Per Samples) / 8

Bit Per Sample

2 Bytes Little Endian

8 또는 16

Extra Pram Size(선택적)

2 Bytes Little Endian

PCM의 경우는 사용하지 않음

Extra Pram(선택적)

N Bytes

확장 옵션을 위해

Sub Chunk 2

Sub Chunk 2 ID

4 Bytes ASCII String

“data”

Sub Chunk 2 Size

4 Bytes Little Endian

Sub Chunk 2의 크기

Data

N Bytes

실제 사운드 데이터

스테레오의 경우 좌 샘플 하나 우 샘플 하나씩 번갈아 가며 저장됨.


PCM의 경우 압축을 사용하지 않으므로 매우 간단하게 읽을 수 있다.

수치 값들은 모두 Little Endian으로 저장되며 모두 Unsigned 값들이다. 실수만 하지 않는 다면 간단하게 읽을 수 있을 것이다.

이곳으로 가면 조금더 자세한 WAV형식에 대한 설명을 볼 수 있습니다.
http://ccrma.stanford.edu/courses/422/projects/WaveFormat/

C#으로 작성해 보았으며 PCM형식의 WAV파일들의 값을읽습니다. 아 그리고 아래 소스는 실행가능 한 코드가 아니고 프로그램의 일부입니다..

또한 스트레오의 필요가 없어서 스트레오의 경우 모노로 바꾸어 읽어 버립니다..

=====================================================================================

using
System;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Windows;
using System.Windows.Forms;

namespace WaveFile

{

    public class Wave

    {

       // string filePath;

        const string ChunkID = "RIFF";

        const string Format = "WAVE";

        const string Subchunk1ID = "fmt ";

        const string Subchunk2ID = "data";

        const int PCM = 1;

        public const int SampleUnit = 1024;

        bool successToOpen = false;

        //Stream waveFileStream;

                            //Offset    size

        string chunkId;     //0         4

        uint chunkSize;     //4         4       전체파일 크기

        string format;      //8         4

        string subchunk1ID; //12        4

        uint subchunk1Size; //16        4

        uint audioFormat;   //20        2

        uint numChannels;   //22        2

        uint sampleRate = 8000;    //24        4 

        uint byteRate;      //28        4       == SampleRate * NumChannels * BitsPerSample/8

        ushort blockAlign;    //32        2       == NumChannels * BitsPerSample/8

        uint bitsPerSample; //34        2       8 bits = 8, 16 bits = 16, etc.

        string subchunk2ID;   //36        4

        uint subchunk2Size; //40        4       == NumSamples * NumChannels * BitsPerSample/8

 

        const int chunkDescriptorSize = 12;

        const int subchunk2HeadSize = 8;

        private string fileName;

 

        private Stream pcmStream = new MemoryStream();

 

        public uint TotalLength

        {

            get { return (this.subchunk2Size-8) / this.BlockAlign ; }

        }

 

        public uint NumChannels

        {

            get { return this.numChannels; }

        }

 

        public uint SampleRate

        {

            get

            {

                return this.sampleRate;

            }

        }

 

        public bool SuccessToOpen

        {

            get

            {

                return successToOpen;

            }

        }

 

        public bool isStreo()

        {

            if (this.numChannels == 2)

                return true;

            return false;

        }

 

        public uint BlockAlign

        {

            get

            {

                return this.blockAlign;

            }

        }

 

        public uint BitsPerSample

        {

            get { return this.bitsPerSample; }

        }

 

        public string FileName

        {

            get{return this.fileName;}

            set { this.fileName = value; }

        }

 

        //현재 위치를 하나 읽음

        public int readSample()

        {

            BinaryReader br = new BinaryReader(this.pcmStream);

            if(br.BaseStream.Position >= br.BaseStream.Length) return 0;

            if (this.isStreo())

            {

                if (this.bitsPerSample == 8)

                {

                    return (int)((br.ReadSByte() + br.ReadSByte())/2);

                }

                else if (this.bitsPerSample == 16)

                {

                    return (int)((br.ReadInt16() + br.ReadInt16()) / 2);

                }

 

            }

            else

            {

                if (this.bitsPerSample == 8)

                {

                    return (int)br.ReadSByte();

                }

                else if (this.bitsPerSample == 16)

                {

                    return (int)br.ReadInt16();

                }

            }

            return 0;

        }

 

        public uint Subchunk2Size

        {

            get{ return this.subchunk2Size;}

        }

 

        public int FrameSize

        {

            get { return (int)(TotalLength / Wave.SampleUnit+1); }

       

        }       

           

          

        //오픈시 헤드를 읽음

        public Wave(Stream stream)

        {

            stream.Seek(0, SeekOrigin.Begin);

            byte[] buffer = new byte[stream.Length];

            stream.Read(buffer, 0, (int)stream.Length);

            this.pcmStream.Write(buffer, 0, (int)stream.Length);

            this.pcmStream.Seek(0, SeekOrigin.Begin);

           

            /////////////////////////////////

            // The "RIFF" chunk descriptor //

            /////////////////////////////////

            this.chunkId = readAscii4Byte(this.pcmStream);

            if (Wave.ChunkID != this.chunkId)

            {

                this.pcmStream.Close();

                //successToOpen = false;

            }

 

            BinaryReader br = new BinaryReader(this.pcmStream);

            this.chunkSize = br.ReadUInt32();

 

            this.format = readAscii4Byte(this.pcmStream);

            if (Wave.Format != this.format)

            {

                this.pcmStream.Close();

                return;

            }

 

            /////////////////////////

            // The "fmt" sub-chunk //

            /////////////////////////

            this.subchunk1ID = readAscii4Byte(this.pcmStream);

            if (this.subchunk1ID != Wave.Subchunk1ID)

            {

                this.pcmStream.Close();

                return;

            }

 

            this.subchunk1Size = br.ReadUInt32();

            this.audioFormat = br.ReadUInt16();

            this.numChannels = br.ReadUInt16();

            this.sampleRate = br.ReadUInt32();

            this.byteRate = br.ReadUInt32();

            this.blockAlign = br.ReadUInt16();

            this.bitsPerSample = br.ReadUInt16();

 

            //부가 정보가 있을 경우 넘김

            if (16 != this.subchunk1Size)

            {

                br.ReadBytes((int)this.subchunk1Size - 16);

            }

 

            //////////////////////////

            // The "data" sub-chunk //

            //////////////////////////

            this.subchunk2ID = readAscii4Byte(this.pcmStream);

            if (this.subchunk2ID != Wave.Subchunk2ID)

            {

                this.pcmStream.Close();

                return;

            }

           

            this.subchunk2Size = br.ReadUInt32();

 

            this.successToOpen = true;

           

            this.dftArray = new int[Wave.SampleUnit];

            //br.Close();

            return;

        } 

        private string readAscii4Byte(Stream fs)

        {

            byte[] buf = new byte[5];

 

            fs.Read(buf, 0, 4);

            buf[4] = 0;

            ASCIIEncoding asciiEn = new ASCIIEncoding();

            return asciiEn.GetString(buf, 0, 4);

           

        } 

 

    }

}


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
c#, pcm, RIFF, wav

일명 매크로 프로그램을 만드는 방법을 간단히 소개하겠다.

가장 먼저해야 할 일은 제어하고자 하는 프로그램의 핸들을 얻어 오는 것이다.

Spy+프로그램을 이용하면 쉽게 찾을 수 있다.

사용자 삽입 이미지

창 찾기 창이 뜨면 찾기 도구를 드래그 하여 원하는 응용 프로그램에 끌어 놓는다. FindWindow로는 최상위 부모 윈도우만 찾을 수 있다. 제어하고자 하는 응용프로그램에 최상위 윈도우에 끌어다 놓는다.
사용자 삽입 이미지


원하는 프로그램을 선택하면 아래와 같이 윈도우 정보가 표시 된다. 여기서 캡션과 클래스를 통해 원하는 윈도우를 찾을 수 있다.
사용자 삽입 이미지

파일-로그 메세지 메뉴을 이용해 창을 찾은후 확인을 누르면 현재 윈도우가 어떠한 메세지를 받는지 모두 볼 수 있다. 여기서 발생하는 이벤트중 키보드, 또는 마우스 관련 이벤트를 확인 하면 된다.
사용자 삽입 이미지


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.Runtime.InteropServices;


namespace WindowsApplication1
{

    public partial class Form1 : Form
    {
        //사용할 API함수를 임포트 한다.
        [DllImport("USER32.DLL")]
        public static extern uint FindWindow(string lpClassName,
            string lpWindowName);

        [DllImport("user32.dll")]
        public static extern uint FindWindowEx(uint hWnd1, uint hWnd2, string lpsz1, string lpsz2);

        [DllImport("user32.dll")]
        public static extern uint SendMessage(uint hwnd, uint wMsg, uint wParam, uint lParam);

        [DllImport("user32.dll")]
        public static extern uint PostMessage(uint hwnd, uint wMsg, uint wParam, uint lParam);

        uint handle;


        private void button3_Click(object sender, EventArgs e)
        {
           //핸들을 찾는다. Spy+를 통해 찾은 클래스 이름과 캡션을 이용하면 된다. 둘 중 하나만 알경우에도 찾을 수 있다. 그때는 하나의 인자를 null로 넘겨 주면된다.
            handle = FindWindow("SciCalc", "계산기");
          //찾은 핸들에서 자식 윈도우 핸들을 찾기 위해서는 FindWindowEx를 이용한다.
            handle = FindWindowEx(handle, 0, "Shell DocObject View", null);
            handle = FindWindowEx(handle, 0, "Internet Explorer_Server", null);
            label1.Text = handle.ToString();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //찾은 핸들을 통해 메세지를 보낸다. 여기서는 아래 16진수들 의 값은 스파이 메세지 로그에서 찾은 값을 이용하면 된다. 여기서는 키를 누른 메세지(탭키누른후 엔터키를 누른다.)를 보내는 예제 이다.
            PostMessage(handle, 0x0100, 0x9, 0xF0001);
            PostMessage(handle, 0x0101, 0x9, 0xC00F0001);
            PostMessage(handle, 0x0100, 0xD, 0x1C001);
            PostMessage(handle, 0x0102, 0xD, 0xC01C001);
           
        }
}


FindWindow 함수는 윈도우를 찾는 API 함수이다. 첫번째 인자는 실행중인 프로그램의 클래스 이름이고 두번째 인자는 프로그램의 타이틀이다. 최상위 부모 윈도우만 찾을 수 있고 자식 윈도우는 FindWinowEx를 통해 찾으면 된다.

FindWindowEx는 부모윈도우 핸들을 통해 부모 윈도의 자식 핸들을 구하는 함수 이다. 예를 들면 프로그램에 자식으로 붙어 있는 버튼등을 찾으려면 FindWindowEx를 이용하면 된다. 첫번째 인자는 부모 핸들이다.

두번째 인자는  처번째 인자의 자식핸들 값 또는 0 값이 오면 된다. 만약 핸들 값이 들어오면 들어온 두번째 핸들 값 이후의 자식만 검색한다. 0 이면 모든 윈도우의 자식을 검색한다.

세번째 인자는 클래스 이름, 네번째 인자는 프로그램타이틀명이다.

PostMessage와 SendMessage는 메세지를 보내는 함수이다. 이 함수를 실행함으로서 원하는 제어를 할 수 있다.
첫번째 인자는 메세지를  보내고자하는 윈도우 핸들의 값이고, 두번째 인자는 보내고자 하는 메세지, 세번째 인자는 wParam ,네번째 인자는 lParam이다. lParam ,wParam은 메세지의 부가정보라고 할수 있다.  WM_LBUTTONDOWN메세지가 발생할때는 마우스의 버튼이 눌러진 위치 등의 부가 정보가 필요한데 이러한 정보를 실어보내는 인자이다. 역시 Spy+를 통해 확인이 가능하다.

PostMessage와 SendMessage의 차이점은 거의 없는데 PostMessage로 보내게 되면 메세지를 해당 윈도우에 던지고 바로 리턴하는 함수이고 SendMessage는 메세지를 보낸후 그 메세지가 처리된후 리턴된다. SendMessage동기화가 필요할때 사용하면 되고, 동기화 필요없이 메세지만 보내려면 PostMessage를 이용하면된다.
신고
크리에이티브 커먼즈 라이선스
Creative Commons License


티스토리 툴바