|
|
|
|
|
|
|
فصل چهارم پردازش صوت : برنامهنويسي و پيادهسازي 1- ساختار مورد نياز براي نگهداري ويژگيهاي صدا 2- انجام پردازش صدا به صورت يك رشتة مستقل |
|
|
|
|
|
|
|
3-
ضبط صدا
براي ضبط صدا و انجام پردازشهاي مرتبط با آن كلاسي به نام HSoundRecorder به صورت زير از كلاس HSoundRunner
مشتق گرديد: class
HSoundRecorder : public HSoundRunner { DECLARE_DYNCREATE(HSoundRecorder) public: HSoundRecorder(); virtual ~HSoundRecorder(); protected: void AddBuffer(); //Message Map For WM_WIM_DATA: afx_msg void OnDataReady(UINT uParm,
LONG lWaveHdr); private: HWAVEIN m_hWaveIn; HShortQueue* m_pQueue; public: BOOL Start(WAVEFORMATEX* pwfex=NULL); BOOL Stop(); short* GetSamples(int& nSamples); BOOL IsRecording(); DECLARE_MESSAGE_MAP() }; براي استفاده از اين كلاس كافي است متدهاي Start و Stop آن فراخواني گردند ولي درك
كامل نحوة عملكرد آن نياز به برخي مقدمات دارد. براي شروع كار ضبط از فراخواني اي.پي.آي زير با پارامترهاي
مناسب براي در اختيار گرفتن يك ابزار ورودي صدا استفاده ميكنيم: MMRESULT
waveInOpen(
LPHWAVEIN phwi, UINT uDeviceID, LPWAVEFORMATEX pwfx, DWORD dwCallback, DWORD dwCallbackInstance, DWORD fdwOpen ); كه در آن phwi اشارهگر به
بافري است كه يك handle به ابزار باز شده براي ورودي
صدا را در اختيار ميگذارد. اين پارامتر ابزاري را براي دسترسي به وسيلة ورودي
صدا در اختيار ميگذارد كه ما در ساير فراخوانيهاي مرتبط به آن نياز داريم لذا
در كلاس تعريف شده متغير m_hWaveIn را براي
ذخيرة اين پارامتر پس از اين فراخواني و دسترسي به آن در ساير متدها در نظر
گرفتهايم. پارامتر uDeviceID شناسة ابزار
ورودي را به تابع ميدهد. ميتوان از شناسة WAVE_MAPPER
استفاده كرد كه با استفاده از آن برنامه سختافزار پيشفرض موجود را كه داراي
قابليت پردازش فرمت انتخاب شده كه توسط پارمتر pwfx
به تابع داده ميشود و ما از عضو دادة m_wfData
از كلاس HSound براي انتخاب مقدار آن استفاده ميكنيم به طور خودكار انتخاب ميكند. پارامتر dwCallback شناسة
پنجره، پردازه يا رشتهاي را كه پيامهاي چندرسانهاي به آن ارسال خواهد شد به
تابع ميدهد كه ما از شناسة رشته (عضو دادة m_dwThreadID)
براي تعيين اين پارامتر استفاده خواهيم نمود. پارامتر بعدي dwCallbackInstance دادة سطح كاربري را كه به ساز و كار فرتخواني callback ارسال ميشود تعيين مينمايد و ما از اين
پارامتر استفاده نخواهيم نمود. پارامتر آخر يعني fdwOpen
پرچمي براي ابزار ورودي است كه سازوكار تفسير پارامترها را مشخص ميكند و چون ما
از سازوكار فراخواني رشتهاي استفاده ميكنيم مقدار آن را برابر با CALLBACK_THREAD انتخاب مينماييم. مقدار بازگشتي در صورت بروز اشكال غيرصفر خواهد بود مسائلي
از قبيل اختصاص يافتن وسيلة ورودي به يك پردازة ديگر به صورتي كه سيستم عامل به
صورت اشتراكي آن را در اختيار نگذارد، كمبود حافظه و ... ممكن است باعث بروز
اشكال شوند كه نوع اشكال با توجه به مقدار بازگشتي مشخص ميگردد و ميتواند به
كاربر اعلام گردد. بعد از در اختيار گرفتن يك وسيلة ورودي لازم است كه براي
عمليات حافظه اختصاص يابد و اين عمل ميتواند با فراخواني AddBuffer
به تعداد كافي صورت گيرد. بعد از تخصيص حافظه توسط فراخواني اي.پي.آي زير عمل ضبط
شروع ميگردد: MMRESULT
waveInStart( HWAVEIN
hwi ); پارامتر وروي همان پارامتر بازگشت با مقدار فراخواني waveInOpen يعني phwi است كه همچنانكه اشاره
شد ما آن را در عضو دادة m_hWaveIn نگهداري ميكنيم.
اين فراخواني نيز مانند قبلي در صورت موفقيتآميز بودن مقدار صفر باز ميگرداند. توضيحات بالا مقدمات كافي را براي درك كد متد Start كه در زير ميآيد فراهم ميآورد: BOOL
HSoundRecorder::Start(WAVEFORMATEX* pwfex) { if(!HSoundRunner::Start(pwfex)) return FALSE; m_pQueue=new HShortQueue; //Open the wave device: if( ::waveInOpen( &m_hWaveIn, WAVE_MAPPER, &m_wfData, m_dwThreadID, 0L, CALLBACK_THREAD ) ) return FALSE; //Add several buffers to queue: for(int i=0;i<3;i++) AddBuffer(); if(::waveInStart(m_hWaveIn)) return FALSE; m_bRunning=TRUE; return TRUE; } اما توابع چندرسانهاي ويندوز سازوكار خاصي براي اضافه كردن
بافر دارند كه ميبايست آنها را در نسخة بازنويسي شدة AddBuffer
اين كلاس لحاظ كنيم. براي اضافه كردن يك بافر ابتدا بايد آن را توسط فراخواني
زير براي استفاده آماده كنيم: MMRESULT
waveInPrepareHeader( HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh ); به جاي پارامتر hwi عضو دادة m_hWaveIn را كه قبلاً در فراخواني waveInOpen
مقدارگذاري شده قرار ميدهيم. پارامتر دوم يك اشارهگر به متغيري با ساختار زير
است: typedef
struct {
LPSTR lpData; DWORD dwBufferLength; DWORD
dwBytesRecorded; DWORD
dwUser; DWORD
dwFlags; DWORD
dwLoops; struct wavehdr_tag * lpNext; DWORD
reserved; }
WAVEHDR; كه لازم است اشارهگر lpData به يك حافظه حاوي تعداد مورد نياز عضو اشاره كند. از آنجا كه ما هر بار
بافري با اندازة m_iBufferSize
در نظر ميگيريم، تعداد خانههاي اين آرايه بر حسب بايت برابر با اندازة بافر
ضرب در حداقل تعداد بلوك براي فرمت انتخاب شده (فيلد nBlockAlign
ساختار WAVEFORMATEX) ميباشد و لازم است كه به اين تعداد حافظه اختصاص داده اشارهگر
lpData را برابر با آدرس آن انتخاب كنيم، در ضمن اندازة بافر اختصاص
داده شده را از طريق فيلد dwBufferLength
به اطلاع تابع استفاده كننده ميرسانيم. پارامتر آخر فراخواني مورد بحث بايد
برابر با اندازة پارامتر دوم بر حسب بايت قرار داده شود. بعد از آماده شدن بافر آن را توسط فراخواني زير به بافرهاي
آماده براي اعمال چندرسانهاي اضافه ميكنيم: MMRESULT
waveInAddBuffer(
HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh ); مانند فراخوانيهاي قبل مقدار خروجي دو فراخواني اخير در
صورت عدم بروز خطا صفر خواهد بود. كد زير پيادهسازي كامل متد بازنويسي شدة AddBuffer را براي كلاس HSoundRecorder
به نمايش ميگذارد: void
HSoundRecorder::AddBuffer() { //new a buffer: char *sBuf= new char[m_wfData.nBlockAlign*m_iBufferSize]; //new a header: LPWAVEHDR pHdr=new WAVEHDR; if(!pHdr) return; ZeroMemory(pHdr,sizeof(WAVEHDR)); pHdr->lpData=sBuf; pHdr->dwBufferLength=
m_wfData.nBlockAlign*m_iBufferSize; //prepare it: ::waveInPrepareHeader( m_hWaveIn, pHdr, sizeof(WAVEHDR) ); //add it: ::waveInAddBuffer(m_hWaveIn, pHdr,
sizeof(WAVEHDR)); HSoundRunner::AddBuffer(); } بعد از آن كه عمل ضبط آغاز شد و پس از پر شدن هر بافر
پيغامي به پنجره يا رشتهاي كه شناسة آن به فراخواني waveInOpen
داده شده ارسال ميگردد كه با شناسة MM_WIM_DATA
ميتوان به آن مراجعه نمود. در هنگام فعال شدن اين پيغام لازم است بافر استفاده
شده براي استفادة مجدد آماده گردد و در ضمن مكان مناسب براي ذخيرة دادههاي ضبط
شده و همچنين نمايش آنها پس از فعال شدن اين پيغام است. از آنجا كه طول زمان ضبط صدا مشخص نيست طول بافري كه
نهايتاً دادهها بايد در آن قرار گيرند قابل پيشبيني نميباشد. از اين رو ما از
يك ساختار كه نوع آن را HShortPocket نامگذاري كردهايم براي ذخيرة دادههاي ارسال شده استفاده مينماييم
و حاصل را در صفي كه در قالب كلاس HShortQueue
پيادهسازي شده درج مينماييم.(اين دو ساختار ربطي به برنامهنويسي پردازش صدا
ندارند و درك عملكرد آنها نياز به توضيح اضافي ندارد لذا در اينجا توضيح داده
نميشوند.) عضو دادة m_pQueue از كلاس HSoundRecorder صفي است كه در سطور قبل در مورد آن بحث شد. ما به شيوة ام.اف.سي براي پيغام MM_WIM_DATA
تابعي به نام OnDataReady ميسازيم كه پارامترهاي آن پارامترهاي ارسالي از طرف پيغام
هستند كه دومين آنها كه حاوي ساختار بافر استفاده شده است براي ما اهميت دارد.
با توجه به توضيحات داده شده درك كد اين تابع ميسر است: void HSoundRecorder::OnDataReady(UINT
uParm, LONG lWaveHdr) { LPWAVEHDR pHdr=(LPWAVEHDR)lWaveHdr; ::waveInUnprepareHeader( m_hWaveIn, pHdr, sizeof(WAVEHDR) ); if(m_bRunning) { //Save Input Data: m_pQueue->InsertItem(
pHdr->dwBytesRecorded/2, (short*)pHdr->lpData ); //Draw Buffer: DrawBuffer( pHdr->dwBytesRecorded/2, (short*)pHdr->lpData ); //reuse the header: ::waveInPrepareHeader( m_hWaveIn, pHdr,
sizeof(WAVEHDR) ); ::waveInAddBuffer( m_hWaveIn, pHdr, sizeof(WAVEHDR) ); return; } //we are stopping: delete pHdr->lpData; delete pHdr; m_nBuffers--; } در صورتي كه متد Stop احضار شده
باشد مقدار m_bRunning برابر با FALSE است در اين هنگام نه تنها نيازي به اضافه كردن بافر نداريم بلكه ميتوانيم
بافرهاي اختصاص داده شده را آزاد كنيم. قسمت آخر كد چنين عملي را انجام ميدهد. پس از انجام عمل و احضار متدStop توسط برنامه لازم است با فراخواني waveInStop عمل ضبط را متوقف كنيم و با فراخواني waveInClose
ابزار ضبط آن را براي استفادة ساير برنامهها آزاد كنيم. علاوه بر اين در اين
هنگام تمامي بافرها ارسال شدهاند و ميتوانيم آن را به صورت يك آراية معمولي
ذخيره كنيم و متغير m_pQueue را آزاد
نماييم: BOOL
HSoundRecorder::Stop() { if(!HSoundRunner::Stop()) return FALSE; ::waveInStop(m_hWaveIn); ::waveInClose(m_hWaveIn); m_pSamples=m_pQueue->ConvertToArray(m_nSamples); delete m_pQueue; return TRUE; } پس از انجام اين عمل برنامه ميتواند با فراخواني GetSamples به آراية حاوي صداي ضبط شده دسترسي پيدا كند: short*
HSoundRecorder::GetSamples(int& nSamples) { nSamples=m_nSamples; return m_pSamples; } اين آرايه در هنگام آزاد شدن متغير از نوع HSoundRecorder در متد ويرانگر آزاد ميشود: HSoundRecorder::~HSoundRecorder() { if(m_pSamples) delete []m_pSamples; } در متد سازندة كلاس پدر مقدا m_pOwner
برابر با NULL در نظر گرفته ميشود. اين به اين معني است كه در حالت پيشفرض
عمليات به صورت گرافيكي نشان داده نميشود و براي انجام اين عمل لازم است كه
ابتدا مقدار m_pOwner به اشارهگر به يك پنجره يا كنترل توسط
فراخواني SetOwner مقدارگذاري
شود. در متد سازندة اين كلاس به منظور جلوگيري از مقدارگزينيهاي ناخواسته مقدار m_hWaveIn برابر با NULL انتخاب ميگردد: HSoundRecorder::HSoundRecorder() { m_hWaveIn =NULL; } 4-
پخش صدا
پردازشهاي مربوط به پخش صدا مشابهت زيادي با پردازشهاي
مربوط به ضبط دارد در اينجا نيز بايد ابتدا يك ابزار صدا را براي خروجي باز كرد،
تعدادي بافر اضافه نمود، در تابع پيام بافرهاي جديد آماده نمود و سرانجام در متد
Stop ابزار خروجي را بست: class
HSoundPlayer: public
HSoundRunner { public: HSoundPlayer(); ~HSoundPlayer(); void SetData(int nSamples, short*
pSamples); BOOL Start(WAVEFORMATEX* format=NULL); BOOL Start( int iSize, short* pData, WAVEFORMATEX* pwfex=NULL ); BOOL Stop(); BOOL IsPlaying(); void AddBuffer(); DECLARE_MESSAGE_MAP() afx_msg void OnMM_WOM_DONE(UINT parm1,
LONG parm2); private: HWAVEOUT m_hWaveOut; int m_nSamplesPlayed; }; تفاوت تنها در اين مورد است كه در اينجا ما بايد دادههاي
آماده را به برنامه بدهيم. به دليل آن كه در برنامههاي ما خروجي هميشه پس از
ورودي مطرح ميگردد و ما پس از پايان ورودي به دادههاي آن دسترسي داريم سادهترين
راه دادن دادهها مقداردهي اشارهگر m_pSamples
با بافر حاوي دادههاي ورودي است كه معمولاُ ما آن را از شيء ضبط كننده ميگيريم: void
HSoundPlayer::SetData(int iSize, short* pData) { m_nSamples=iSize; m_pSamples=pData; } به منظور افزايش انعطافپذيري ميتوان اين دادهها را در
متد Start نيز دريافت نمود: BOOL
HSoundPlayer::Start( int iSize, short* pData, WAVEFORMATEX* format ) { SetData(iSize, pData); return Start(format); } فراخواني waveOutOpen عملي مشابه waveInOpen را براي خروجي صدا انجام ميدهد و پارامترهاي آن مشابه با آن
است: BOOL
HSoundPlayer::Start(WAVEFORMATEX* format) { if(m_pSamples==NULL) return FALSE; if(!HSoundRunner::Start(format)) return FALSE; else { // open wavein device MMRESULT mmReturn = 0; mmReturn = ::waveOutOpen(
&m_hWaveOut,
WAVE_MAPPER,
&m_wfData,
m_dwThreadID, NULL,
CALLBACK_THREAD ); if(mmReturn) return FALSE; else { m_bRunning =
TRUE; m_nSamplesPlayed=0; for(int i=0;
i<3; i++) AddBuffer(); } } return TRUE; } در اينجا تفاوتي نيز وجود دارد. در فرايند ضبط اين برنامة
كاربر بود كه پيغام Stop را ارسال ميكرد حال آن كه در
اينجا علاوه بر كاربر، تمام شدن بافر حاوي دادهها نيز بايد باعث فعال شدن آن
پيغام شود. براي اين منظور از متغير شمارشگري به نام m_pSamplesPlayed استفاده كردهايم كه تعداد نمونههاي پخش شده بر
حسب تعداد حافظة short (كه نصف
تعداد نمونه در حافظة معادل بر حسب بايت است) را ذخيره ميكند. تفاوت ديگري نيز وجود دارد و آن اين است كه ما بايد توسط
فراخواني waveOutWrite دادهها را روي بافر ارسالي بنويسيم: void
HSoundPlayer::AddBuffer() { MMRESULT mmReturn = 0; // create the header LPWAVEHDR pHdr = new WAVEHDR; if(pHdr == NULL) return; // new a buffer pHdr->lpData= (char*)(m_pSamples+
m_nSamplesPlayed);//buffer; pHdr->dwBufferLength =
m_iBufferSize; pHdr->dwFlags = 0; // prepare it mmReturn= ::waveOutPrepareHeader( m_hWaveOut,
pHdr, sizeof(WAVEHDR) ); // write the buffer to output queue mmReturn = ::waveOutWrite( m_hWaveOut, pHdr, sizeof(WAVEHDR) ); if(mmReturn) return; // increment the number of waiting
buffers m_nSamplesPlayed+=m_iBufferSize/2; HSoundRunner::AddBuffer(); } در انجام عمل پخش نيز پيغامي پس از پايان پخش هر بافر با
شناسة MM_WON_DONE به پنجره يا رشتة كنترل كننده ارسال ميشود كه در آن ميتوانيم
دادة پخش شده را به صورت گرافيكي نشان دهيم، بافر بعدي را بفرستيم و در صورت
رسيدن به پايان دادهها پيغام Stop را ارسال
كنيم: void HSoundPlayer::OnMM_WOM_DONE(UINT
parm1, LONG parm2) { LPWAVEHDR pHdr = (LPWAVEHDR) parm2; //Draw Buffer: DrawBuffer( pHdr->dwBufferLength/2, (short*)pHdr->lpData ); if( ::waveOutUnprepareHeader( m_hWaveOut, pHdr,
sizeof(WAVEHDR) ) ) return; m_nBuffers--; if(m_bRunning) { if(!(m_nSamplesPlayed+m_iBufferSize/2>=m_nSamples)) { AddBuffer(); // delete old
header delete pHdr; return; } else { Stop(); } } // we are closing the waveOut handle, // all data must be deleted // this buffer was allocated in Start() delete pHdr; if(m_nBuffers == 0 &&
m_bRunning == false) { if
(::waveOutClose(m_hWaveOut)) return; } } اگر دقت كرده باشيد در هنگام رسيدن به پايان دادهها در
همين تابع اخير ما ابزار خروجي را ميبنديم. اگر زودتر پيغام Stop
توسط برنامه ارسال شود نيز به لحاظ FALSE شدن مقدار m_bRunning اين عمل انجام ميپذيرد لذا كافي است كه در متد Stop
ابزار براي استفادة بعدي به طور كامل آزاد گردد: BOOL
HSoundPlayer::Stop() { if(HSoundRunner::Stop()) return (::waveOutReset(m_hWaveOut)!=0); return FALSE; } از آنجا كه در اين كلاس حافظهاي اختصاص داده نميشود در
متد ويرانگري نيز آزاد شدن حافظه انجام نميگيرد. مشابه متد سازندة كلاس HSoundPlayer مقدار m_hWaveOut در ابتدا
برابر با NULL در نظر گرفته ميشود: HSoundPlayer::HSoundPlayer() { m_hWaveOut = NULL; } 5-
كتابخانة پردازش صوت
از آنجا كه كلاسهاي پيادهسازي شده كه در اين فصل توضيح
داده شدند مورد استفادة بيش از يك برنامه قرار گرفتهاند آنها را به صورت يك
كتابخانة ايستا و با نام HSoundLib گردآوري نموديم تا تغيير كد اين كتابخانه راحتتر در تمامي برنامهها
اعمال گردد و نياز به تغيير كد تك تك آنها نباشد. 6-
منابع فصل
1)
Microsoft
®, MSDN Library (January 2000 Edition) 2)
Thomas
Holme, How to play and record sound, from http://www.codeproject.com |
7-
|
|
|
|