قسمت بعدي

قسمت قبلي

 

فهرست مطالب

صفحة فعاليتها

 

 

 

 

 

 

 

فصل چهارم

 

پردازش صوت : برنامه‌نويسي و پياده‌سازي

 

 

1- ساختار مورد نياز براي نگهداري ويژگيهاي صدا

2- انجام پردازش صدا به صورت يك رشتة مستقل

3- ضبط صدا

4- پخش صدا

5- كتابخانة پردازش صوت

6- منابع فصل

 

 

 

 

 

 

 

 

1-     ساختار مورد نياز برای نگهداری ويژگيهای صدا

 

 

همچنان که در فصل پيش اشاره شد برای ذخيره يا بازخوانی يک نمونه صدا به صورت ديجيتال نيازمند آنيم که برخی ويژگيهای خاص صدای ديجيتالی از قبيل نرخ نمونه‌برداری، تعداد بيت هر نمونه و يك‌كاناله يا دوكاناله بودن صدا را مشخص كنيم.

براي اين منظور در محيط برنامه‌نويسي مورد نظر ما (ويندوز) از ساختاري به نام WAVEFORMATEX استفاده مي‌گردد كه به صورت زير تعريف مي‌گردد:

 

typedef struct { 
    WORD  wFormatTag; 
    WORD  nChannels; 
    DWORD nSamplesPerSec; 
    DWORD nAvgBytesPerSec; 
    WORD  nBlockAlign; 
    WORD  wBitsPerSample; 
    WORD  cbSize; 

} WAVEFORMATEX;

 

در اين ساختار فيلد wFormatTag فرمت فايل را كه نشان دهندة نوع الگوريتمهاي به كار گرفته شده براي فشرده‌سازي صدا و... است را مشخص مي‌كند. براي استفادة مورد نظر ما فرمت خاصي كه با ثابت WAVE_FORMAT_PCM مشخص مي‌گردد و فرمت پي.سي.ام[1] ناميده مي‌شود مناسب است. علاوه بر آن فيلد cbSize براي فرمتهاي غير پي.سي.ام استفاده مي‌شود و ما همواره مقدار آن را صفر در نظر خواهيم گرفت.

از آنجا كه پردازش اين ساختار در برنامه‌نويسي صدا براي پروژة مورد نظر بارها صورت مي‌گيرد و از آنجا كه يك شيوة طراحي شيءگرا (شيوة ام.اف.سي[2])براي پياده‌سازي پروژه در نظر گرفته شده بود و از آنجا كه پردازش اين ساختار نياز به برخي محاسبات تكراري (تعيين nBlockAlign و nAvgBytesPerSec) دارد و به چند دليل ديگر تصميم گرفته شد كه اين ساختار و پردازش آن به صورت يك كلاس با نام HSound پياده سازي گردد كه ضمن خودكار نمودن پردازش اين ساختار كلاسهايي كه به اعمال پخش و ضبط را بر عهده دارند از اين كلاس ارث‌بري نموده برنامه نويسي را آسان‌تر و كد به دست آمده را خواناتر نمايند.

 تعريف اين كلاس به صورت زير است:

 

class HSound

{

public:

 

            //constructor and destructor:

            HSound();

            virtual ~HSound();

 

            //setting wave data:

            void SetBitsPerSample(int bps);

       void SetSamplesPerSecond(int sps);

       void SetNumberOfChannels(int nchan);

 

       //retrieving wave data:

       WAVEFORMATEX* GetFormat();

       int GetSamplesPerSecond();

       int GetBitsPerSample();

       int GetNumberOfChannels();

 

protected:

 

       WAVEFORMATEX m_wfData;

 

private:

 

       void Update();

};

 

قبل از هر چيز بايد به اين نكته اشاره شود كه اين كلاس براي پردازش فرمت پي.سي.ام در نظر گرفته شده، لذا ضمن تعريف مقادير پيش‌فرض لازم براي اين فرمت و استفاده از روشهاي خاص اين فرمت براي محاسبة فيلدهاي مختلف امكان تغيير فرمت و استفاده از ساير فرمتها را به برنامه‌نويس نمي‌دهد و به منظور پردازش ساير فرمتها ساختار كلاس مي‌بايست تغيير كند.

فيلد wBitsPerSample تعداد بيتِ هر نمونه را مشخص ميكند كه براي فرمت پي.سي.ام فقط مي‌تواند يكي از دو مقدار 8 و 16 را داشته باشد و براي ساير فرمتها مقادير ممكن بستگي به مشخصات منتشر شده توسط شركتهاي به وجود آورنده و پشتيباني‌كنندة آنها دارد.

متدي كه در پي مي‌آيند آن را مقدارگذاري مي‌كند (در مورد متد Update و علت فراخواني آن در ادامه توضيح داده خواهد شد) :

 

void HSound::SetBitsPerSample(int bps)

{

       m_wfData.wBitsPerSample = bps;

       Update();

}

 

 

و متد زير مقدار انتخاب شده را برمي‌گرداند:

 

int HSound::GetBitsPerSample()

{

       return m_wfData.wBitsPerSample;   

}

 

فيلد nSamplesPerSec تعداد نمونه‌ها در هر ثانيه (نرخ نمونه‌برداري) را مشخص مي‌كند. براي فرمت پي.سي.ام مقادير معمول 8كيلوهرتز (8000)، 11.025كيلوهرتز (11025)، 22.05كيلوهرتز (22050) و 44.1كيلوهرتز (44100) مي‌باشد و براي ساير فرمتها مقادير ممكن بستگي به مشخصات منتشر شده توسط شركتهاي به وجود آورنده و پشتيباني‌كنندة آنها دارد.

متد مقدارگذاري اين فيلد:

 

void HSound::SetSamplesPerSecond(int sps)

{

       m_wfData.nSamplesPerSec = sps;

       Update();

}

 

و متد دريافت مقدار آن:

 

int HSound::GetSamplesPerSecond()

{

       return m_wfData.nSamplesPerSec;

}

 

فيلد nChannels تعداد كانالهاي موج صوتي را مشخص مي‌كنند. صداهاي تك كانال (مقدار فيلد برابر با 1) مونو و صداهاي دوكاناله (مقدار فيلد برابر با 2) استريو خواهند بود.

متد مقدار‌گذاري اين فيلد:

 

void HSound::SetNumberOfChannels(int nchan)

{

       m_wfData.nChannels = nchan;

       Update();

}

 

و متد دريافت مقدار آن:

 

int HSound::GetNumberOfChannels()

{

       return m_wfData.nChannels;

}

 

هر چند تعداد كانالهاي صدا در اين كلاس قابل تغييراست اما در كلاسهاي مشتق شده همواره الگوريتمها براي موج صوتي تك‌كاناله نوشته شده‌اند و استفاده از آنها براي پردازش موج صوتي دو كاناله نيازمند دستكاري كد اين كلاسهاست كه به لحاظ استفاده‌اي كه ما از اين كلاسها نموده‌ايم يك كار اضافي و غيرضروري به نظر مي‌رسد.

فيلد nBlockAlign كمينة تعداد واحد داده را براي فرمت انتخاب شده تعييد مي‌كند كه اگر فرمت انتخاب شده پي.سي.ام باشد برابر با حاصل ضرب تعداد كانالها (nChannels) در تعداد بيتِ هر نمونه (nBitsPerSample) تقسيم بر تعداد بيتهاي موجود در هر بايت (8) خواهد بود و براي ساير فرمتها بستگي به مشخصات منتشر شده توسط شركتهاي به وجود آورنده و پشتيباني‌كنندة آنها دارد. فيلد nAvgBytesPerSec نيز تعداد متوسط بايتهاي موجود در هر ثانية صدا را مشخص مي‌كند و براي فرمت پي.سي.ام برابر با تعداد نمونه‌هاي موجود در هر ثانيه (nSamplesPerSec) در كمينة تعداد واحد داده (nBlockAlign) خواهد بود و براي ساير فرمتها بستگي به مشخصات منتشر شده توسط شركتهاي به وجود آورنده و پشتيباني‌كنندة آنها دارد.

متد Update كه در كد مقدار‌گذاري ساير فيلد‌ها محاسبات توضيح داده شدة بالا را انجام مي‌دهد:

 

void HSound::Update()

{

       m_wfData.nBlockAlign=

    m_wfData.nChannels

    *

    (m_wfData.wBitsPerSample/8);

 

       m_wfData.nAvgBytesPerSec=

   m_wfData.nSamplesPerSec

   *

   m_wfData.nBlockAlign;                                          

}

 

در صورتي كه نياز باشد با ساختار اصلي WAVEFORMATEX كار شود متد زير مقدار عضوي از كلاس را كه از اين نوع است باز مي‌گرداند:

 

WAVEFORMATEX* HSound::GetFormat()

{

       return &m_wfData;

}

 

در متد سازندة اين كلاس به طور پيش‌فرض براي نمونة صوتي مورد نظر نرخ نمونه‌برداري 44.1كيلوهرتز با 16 بيت در هر نمونه در نظر گرفته شده و فرض بر آن است كه نمونة صوتي يك كاناله است:

 

HSound::HSound()

{

       m_wfData.wFormatTag      = WAVE_FORMAT_PCM;

       m_wfData.cbSize              = 0;

       SetBitsPerSample(16);

       SetSamplesPerSecond(44100);

       SetNumberOfChannels(1);

}

 

همچنانكه از روي تعريف كلاس قابل فهم است اين كلاس در واقع تمامي اعمال را روي عضو دادة محافظت شدة m_wfData اِعمال مي‌نمايد و با غير مستقيم نمودن دسترسي به اين عضو داده براي برنامة استفاده كننده ضمن رعايت اصل پنهانسازي اطلاعات به فراخواني روية Update در متدهاي تغيير دهندة اعضاي مرتبط با nBlockAlign و nAvgBytesPerSec تغييرات لازم را به آنها اعمال مي‌كند.

 

2-     انجام پردازش صدا به صورت يك رشتة[3] مستقل

 

مي‌توان با استفاده از توابع كار با صداي ويندوز به گونه‌اي برنامه‌نويسي نمود كه نيازي به ايجاد رشته‌هاي مستقل براي پردازنده‌هاي صدا نباشد، اما وجود دلايلي از قبيل عدم انعطاف‌پذيري اين روش و تك‌وظيفه‌اي شدن برنامه در حين انجام عمليات پردازش صدا باعث مي‌شود كه روش استفاده از رشته‌هاي مستقل مورد توجه ما قرار گيرد.

از آغاز در نظر داشتيم كه رابط برنامه به گونه طراحي شود كه كاربر در هنگام كار با برنامه و انجام عملياتي نظير ضبط صدا از عملكرد برنامه مطمئن باشد. به اين معني كه مثلاً در حين هنگام صدا با استفاده از يك رابط گرافيكي مانند يك نمايشگر اسيلوسكوپي از اين كه برنامه واقعاً و به درستي در حال ضبط صداي اوست و يا به لحاظ فاصلة نامتناسب با ميكروفن يا عدم اتصال درست آن به كارت صوتي يا خرابي آن بيشتر آنچه ضبط مي‌شود سكوت و يا نويز است مطلع گردد. يك روش مناسب براي ايجاد چنين رابطي استفاده از پيام فرستاده شده براي پردازش صدا توسط يك رشته براي فعال شدن يك تابع رسم‌كنندة نمودار اسيلوسكوپي است كه نياز به آن دارد كه بدون قطع شدن جريان ضبط پردازش ديگري صورت گيرد. به اين منظور و با استفاده از كد اوليه‌اي كه در منبع شمارة 2 به آن اشاره شده كلاسي به نام HSoundRunner را از كلاس HSound اعضاي داده و متدهاي مرتبط با پردازش صوت و از كلاس ام.اف.سي CwinThread اعضاي داده و متدهاي لازم براي يك رشته را ارث‌بري مي‌كند به صورت زير تعريف نموديم:

 

class HSoundRunner:

       public CWinThread,

       public HSound

{

public:

       DECLARE_DYNCREATE(HSoundRunner)

 

       HSoundRunner();

       ~HSoundRunner();

 

       void       SetBufferSize(int nSamples);

       int         GetBufferSize();

 

       //this metheods should be overriden:

       void AddBuffer();

       BOOL Start(WAVEFORMATEX* pwfex=NULL);

       BOOL Stop();

 

       //for graphical display:

       void SetOwner(CWnd* pWnd);

       void ClearOwner(COLORREF crBkColor=0x000000);

 

public:

 

       //{{AFX_VIRTUAL(HSoundRecorder)

       public:

       virtual BOOL InitInstance();

       //}}AFX_VIRTUAL

 

protected:

       DWORD m_dwThreadID;

 

       int m_iBufferSize;  // number of samples per each period

       int m_nBuffers;                //number of buffers remained to be run

 

       int m_nSamples;               //number of samples stored

       short* m_pSamples;          //samples stored

 

       BOOL m_bRunning;           //indicated running or not   

 

//if graphical display is intended set this value

       CWnd* m_pOwner;                      

 

       void DrawBuffer(

                int nSamples,

                short* pSamples,

                COLORREF crBkColor=0x000000,

                COLORREF crLineColor=0x00FF00

               );         

};

 

اعضاي دادة اين كلاس در روند انجام عمليات توسط كلاسهاي مشتق شده از آنها نقش خود را نشان خواهند داد و به عنوان نمونه عضو دادة m_iBufferSize كه نشان دهندة آن است كه بعد از ضبط با پخش چند نمونه تابع پزدازندة پيام در كلاس پنجرة كنترل كننده بايد فراخواني شود در اين هيچكدام از متدهاي اين كلاس نقش عملي پيدا نمي‌كند و فقط مقدارگذاري آن از طريق متد SetBufferSize و ديافت مقدار فعلي آن از طريق GetBufferSize صورت مي‌گيرد:

 

void HSoundRunner::SetBufferSize(int nSamples)

{

       m_iBufferSize=nSamples;

}

 

int HSoundRunner::GetBufferSize()

{

       return m_iBufferSize;

}

 

عضو دادة m_dwThreadID مقدار شناسة رشتة ايجاد شده را كه در كلاس سازنده با فراخواني CreateThead ايجاد مي‌شود در بر مي‌گيرد كه در كلاسهاي مشتق شده براي كار با فراخوانيهاي اي.پي.آي پردازش صدا كاربرد پيدا مي‌كند. مقدار اين عضو داده در متد بازنويسي[4] شدة InitInstance و به صورت زير تعيين مي‌گردد:

 

BOOL HSoundRunner::InitInstance()

{

       m_dwThreadID = ::GetCurrentThreadId();

       return TRUE;

}

 

اعضاي دادة m_nSamples و m_pSamples به ترتيب تعداد نمونه‌هاي ضبط شده يا آماده براي پخش و آراية حاوي آنها -كه به لحاظ آسان‌تر شدن كار با كتابخانه‌اي كه براي پردازش سيگنال صحبت استفاده شده و همسان با آن از نوع short در نظر گرفته شده- را نشان مي‌دهند. اين دو عضو داده به صورت پويا پس از انجام عمليات ضبط مقدارگذاري مي‌شوند و براي عمليات پخش بايد قبلاً مقدارگذاري شده باشند.

آنچه اين كلاس انجام مي‌دهد غير از ايجاد يك رشته براي انجام پردازش صدا فراهم آوردن روشي براي نمايش اسيلوسكوپي صداست كه از طريق متد DrawBuffer انجام مي‌شود.

اين متد در توابع پيامهاي مربوط به پردازش صوت خود به خود فراخواني مي‌گردد و در صورتي كه مقدار m_pOwner اشاره‌گر به يك پنجره يا كنترل انتخاب شود (توسط متد SetOwner) آراية ورودي كه معمولاً يك تكة تازه ضبط با پخش شده از كل صداست متناسب با طول و عرض پنجرة مورد نظر بر روي آن كشيده مي‌شود. اين كار با ايجاد يك ابزار متن[5] و يك بيتمپ متناسب با ابزار متن پنجرة مورد نظر، كشيدن طرح لازم با استفاده از اين دو و در نهايت نمايش تصوير ايجاد شده بر روي پنجرة مقصد و مطابق با كد زير انجام مي‌شود:

 

void HSoundRunner::DrawBuffer(

                           int nSamples,

                           short* pSamples,

                           COLORREF crBkColor,

                           COLORREF crLineColor

                          )

{

       if(m_pOwner==NULL)

                    return;

 

       CRect rc;

       m_pOwner->GetClientRect(&rc);

       int iWidth=rc.Width();

       int iHeight=rc.Height();

 

       CDC* pDC=m_pOwner->GetDC();

 

       CBitmap Bitmap;

       Bitmap.CreateCompatibleBitmap(pDC, iWidth, iHeight);

 

       CDC dc;

       dc.CreateCompatibleDC(pDC);

 

       dc.SelectObject(&Bitmap);

       CBrush Brush(crBkColor);

       dc.FillRect(&rc,&Brush);

       CPen Pen(PS_SOLID,1,crLineColor);

       dc.SelectObject(&Pen);

       dc.SetBkColor(crBkColor);

       if(GetBitsPerSample()==16)

       {

                    float fx=iWidth/float(nSamples);

                    float fy=float(iHeight/32767.0);

                    dc.MoveTo(0, iHeight/2);

                    int i=0;

                    for(

         float f=0; f<iWidth&&i<nSamples; f+=fx, i++

           )

                                dc.LineTo(

              int(f), int(iHeight/2+fy*pSamples[i])

                      );

                    pDC->BitBlt(

              0, 0,

              iWidth, iHeight,

              &dc,

              0, 0,

              SRCCOPY

                  );

       }

}

 

متد ClearBuffer يك روش قابل دسترسي توسط برنامه براي پاك كردن پنجرة مورد استفاده به وجود مي‌آورد و شامل يك فراخواني متد محافظت شدة DrawBuffer با يك آراية به طول صفر است:

 

void HSoundRunner::ClearOwner(COLORREF crBkColor)

{

       DrawBuffer(0,NULL,crBkColor);

}

 

عضو دادة m_nBuffers تعداد بافرهاي اختصاص داده شده و استفاده نشده را نشان مي‌دهد كه در متد AddBuffer اين كلاس و بازنويسي شدة آن براي كلاسهاس مشتق شده مقدار آن به ازاي هر بار فراخواني يك واحد افزوده مي‌شود و در پيامهاي پردازش صدا كه از طرف سيستم عامل فعال مي‌شوند و نشانگر استفاده شدن بافر مورد نظر است (در كلاسهاي مشتق شده) يك واحد كاهش مي‌يابد. در نهايت صفر نبودن اين عضو داده نشانگر استفادة ناكامل از بافرهاي اختصاص داده شده (معادل با كامل انجام نشدن فرايند ضبط يا پخش) است كه مي‌تواند پردازش مناسب براي آن صورت گيرد:

 

void HSoundRunner::AddBuffer()

{

       m_nBuffers++;

}

 

عضو دادة m_bRunning به منظور تشخيص اين كه برنامه در حال اجراي عمليات پردازش صداست و يا نه به منظور جلوگيري از ايجاد اشكال با فراخوانيهاي تكراري در نظر گرفته شده كه در هنگام آغاز عمليات مقدار آن برابر با TRUE و در پايان آن برابر با FALSE انتخاب مي‌گردد. همچنان كه در ادامه توضيح داده خواهد شد كلاسهاي مشتق شده متدهايي از لحاظ نام متناسب با عملي كه براي آن در نظر گرفته شده‌اند (ضبط يا پخش) براي بازگرداندن اين مقدار به برنامه‌نويس كاربر اين كلاسها دارند:

 

BOOL HSoundRunner::Start(WAVEFORMATEX* pwfex)

{

       if(m_bRunning)

                    return FALSE;

       if(pwfex != NULL)            

                    m_wfData = *pwfex;

       return TRUE;

}

 

BOOL HSoundRunner::Stop()

{

       if(m_bRunning)

       {

                    m_bRunning=FALSE;

                    Sleep(500);

                    return TRUE;

       }

       return FALSE;

}

 

فراخواني استاندارد Sleep در متد Stop براي مصرف كامل بافر ايجاد شده انجام مي‌گردد. در ضمن متد Start روشي براي جايگزيني مقدار پيش‌فرض m_wfData (عضو كلاس Hsound) با مقدار جديد در اختيار مي‌گذارد.

در متد سازنده اعضاي داده با مقادير پيش‌فرض مقدارگذاري شده و رشتة مورد نظر با فراخواني CreateThead ايجاد مي‌گردد:

 

HSoundRunner::HSoundRunner()

{

       m_iBufferSize= 2048;

       m_nBuffers = 0;

       m_bRunning = FALSE;

       m_nSamples=0;

       m_pSamples=NULL;

 

       m_pOwner=NULL;

 

       CreateThread();

}

 

در متد ويرانگر[6] نيز در صورتي كه شيء از نوع اين كلاس در حال انجام عمليات پردازش صوت باشد متوقف خواهد شد:

 

HSoundRunner::~HSoundRunner()

{

       if(m_bRunning)

                    Stop();

}

 

به لحاظ آن كه آراية داده‌ها (m_pSamples) در اين كلاس ايجاد نمي‌گردد در متد ويرانگر آزاد شدن آن پيشبيني نشده است.

 

ادامة فصل

 

 

 

 



[1] PCM

[2] MFC

[3] thread

[4] overriden

[5] Device Context (DC)

[6] destructor