Newer
Older
monitord / lame-3.97 / ACM / .svn / text-base / ACM.cpp.svn-base
@root root on 23 Jan 2012 48 KB Migration from SVN revision 455
  1. /**
  2. *
  3. * Lame ACM wrapper, encode/decode MP3 based RIFF/AVI files in MS Windows
  4. *
  5. * Copyright (c) 2002 Steve Lhomme <steve.lhomme at free.fr>
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  20. *
  21. */
  22. /*!
  23. \author Steve Lhomme
  24. \version \$Id: ACM.cpp,v 1.18 2005/03/13 14:43:21 robert Exp $
  25. */
  26.  
  27. #if !defined(STRICT)
  28. #define STRICT
  29. #endif // STRICT
  30.  
  31. #include <algorithm>
  32.  
  33. #include <windows.h>
  34. #include <windowsx.h>
  35. #include <intshcut.h>
  36.  
  37. #include <mmreg.h>
  38. #include <msacm.h>
  39. #include <msacmdrv.h>
  40.  
  41. #include <assert.h>
  42.  
  43. #include <version.h>
  44.  
  45. #include "adebug.h"
  46. #include "resource.h"
  47. #include "ACMStream.h"
  48.  
  49. #ifdef ENABLE_DECODING
  50. #include "DecodeStream.h"
  51. #endif // ENABLE_DECODING
  52.  
  53. #include "ACM.h"
  54.  
  55. #ifndef IDC_HAND
  56. #define IDC_HAND MAKEINTRESOURCE(32649)
  57. #endif // IDC_HAND
  58.  
  59. char ACM::VersionString[20];
  60.  
  61. const char ACM_VERSION[] = "0.9.0";
  62.  
  63. #ifdef WIN32
  64. //
  65. // 32-bit versions
  66. //
  67. #if (WINVER >= 0x0400)
  68. #define VERSION_ACM_DRIVER MAKE_ACM_VERSION(4, 0, 0)
  69. #else
  70. #define VERSION_ACM_DRIVER MAKE_ACM_VERSION(3, 51, 0)
  71. #endif
  72. #define VERSION_MSACM MAKE_ACM_VERSION(3, 50, 0)
  73.  
  74. #else
  75. //
  76. // 16-bit versions
  77. //
  78. #define VERSION_ACM_DRIVER MAKE_ACM_VERSION(1, 0, 0)
  79. #define VERSION_MSACM MAKE_ACM_VERSION(2, 1, 0)
  80.  
  81. #endif
  82.  
  83. #define PERSONAL_FORMAT WAVE_FORMAT_MPEGLAYER3
  84. #define SIZE_FORMAT_STRUCT sizeof(MPEGLAYER3WAVEFORMAT)
  85. //#define SIZE_FORMAT_STRUCT 0
  86.  
  87. //static const char channel_mode[][13] = {"mono","stereo","joint stereo","dual channel"};
  88. static const char channel_mode[][13] = {"mono","stereo"};
  89. static const unsigned int mpeg1_freq[] = {48000,44100,32000};
  90. static const unsigned int mpeg2_freq[] = {24000,22050,16000,12000,11025,8000};
  91. static const unsigned int mpeg1_bitrate[] = {320, 256, 224, 192, 160, 128, 112, 96, 80, 64, 56, 48, 40, 32};
  92. static const unsigned int mpeg2_bitrate[] = {160, 144, 128, 112, 96, 80, 64, 56, 48, 40, 32, 24, 16, 8};
  93.  
  94. #define SIZE_CHANNEL_MODE (sizeof(channel_mode) / (sizeof(char) * 13))
  95. #define SIZE_FREQ_MPEG1 (sizeof(mpeg1_freq) / sizeof(unsigned int))
  96. #define SIZE_FREQ_MPEG2 (sizeof(mpeg2_freq) / sizeof(unsigned int))
  97. #define SIZE_BITRATE_MPEG1 (sizeof(mpeg1_bitrate) / sizeof(unsigned int))
  98. #define SIZE_BITRATE_MPEG2 (sizeof(mpeg2_bitrate) / sizeof(unsigned int))
  99.  
  100. static const int FORMAT_TAG_MAX_NB = 2; // PCM and PERSONAL (mandatory to have at least PCM and your format)
  101. static const int FILTER_TAG_MAX_NB = 0; // this is a codec, not a filter
  102.  
  103. // number of supported PCM formats
  104. static const int FORMAT_MAX_NB_PCM =
  105. 2 * // number of PCM channel mode (stereo/mono)
  106. (SIZE_FREQ_MPEG1 + // number of MPEG 1 sampling freq
  107. SIZE_FREQ_MPEG2); // number of MPEG 2 sampling freq
  108.  
  109. //////////////////////////////////////////////////////////////////////
  110. //
  111. //////////////////////////////////////////////////////////////////////
  112. bool bitrate_item::operator<(const bitrate_item & other_bitrate) const
  113. {
  114. return (other_bitrate.frequency < frequency ||
  115. (other_bitrate.frequency == frequency &&
  116. (other_bitrate.bitrate < bitrate ||
  117. (other_bitrate.bitrate == bitrate &&
  118. (other_bitrate.channels < channels)))));
  119. }
  120.  
  121. //////////////////////////////////////////////////////////////////////
  122. // Configuration Dialog
  123. //////////////////////////////////////////////////////////////////////
  124. /*
  125. static CALLBACK ConfigProc(
  126. HWND hwndDlg, // handle to dialog box
  127. UINT uMsg, // message
  128. WPARAM wParam, // first message parameter
  129. LPARAM lParam // second message parameter
  130. )
  131. {
  132. BOOL bResult;
  133.  
  134. switch (uMsg) {
  135. case WM_COMMAND:
  136. UINT command;
  137. command = GET_WM_COMMAND_ID(wParam, lParam);
  138. if (IDOK == command)
  139. {
  140. EndDialog(hwndDlg, (IDOK == command));
  141. } else if (IDCANCEL == command)
  142. {
  143. EndDialog(hwndDlg, (IDOK == command));
  144. }
  145. bResult = FALSE;
  146. break;
  147. default:
  148. bResult = FALSE; // will be treated by DefWindowProc
  149. }
  150. return bResult;
  151. }
  152.  
  153.  
  154. inline DWORD ACM::Configure(HWND hParentWindow, LPDRVCONFIGINFO pConfig)
  155. {
  156. my_debug.OutPut(DEBUG_LEVEL_FUNC_START, "ACM : Configure (Parent Window = 0x%08X)",hParentWindow);
  157.  
  158. DialogBoxParam( my_hModule, MAKEINTRESOURCE(IDD_CONFIG), hParentWindow, ::ConfigProc , (LPARAM)this);
  159.  
  160. return DRVCNF_OK; // Can also return
  161. // DRVCNF_CANCEL
  162. // and DRVCNF_RESTART
  163. }
  164. */
  165. //////////////////////////////////////////////////////////////////////
  166. // About Dialog
  167. //////////////////////////////////////////////////////////////////////
  168.  
  169. static BOOL CALLBACK AboutProc(
  170. HWND hwndDlg, // handle to dialog box
  171. UINT uMsg, // message
  172. WPARAM wParam, // first message parameter
  173. LPARAM lParam // second message parameter
  174. )
  175. {
  176. static HBRUSH hBrushStatic = NULL;
  177. // static LOGFONT lf; // structure for font information
  178. // static HFONT hfnt;
  179. static HCURSOR hcOverCursor = NULL;
  180. BOOL bResult;
  181.  
  182. switch (uMsg) {
  183. case WM_INITDIALOG:
  184. char tmp[150];
  185. wsprintf(tmp,"LAME MP3 codec v%s", ACM::GetVersionString());
  186. ::SetWindowText(GetDlgItem( hwndDlg, IDC_STATIC_ABOUT_TITLE), tmp);
  187.  
  188. /*
  189. ::GetObject(::GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT), &lf);
  190. lf.lfUnderline = TRUE;
  191.  
  192. hfnt = ::CreateFontIndirect(&lf);
  193.  
  194. ::SendMessage(::GetDlgItem(hwndDlg,IDC_STATIC_ABOUT_URL), WM_SETFONT, (WPARAM) hfnt, TRUE);
  195. * /
  196. hBrushStatic = ::CreateSolidBrush(::GetSysColor (COLOR_BTNFACE));
  197. */ hcOverCursor = ::LoadCursor(NULL,(LPCTSTR)IDC_HAND);
  198. if (hcOverCursor == NULL)
  199. hcOverCursor = ::LoadCursor(NULL,(LPCTSTR)IDC_CROSS);
  200.  
  201. bResult = TRUE;
  202. break;
  203. /*
  204. case WM_CTLCOLORSTATIC:
  205. /// \todo only if there are URLs
  206. if ((HWND)lParam == ::GetDlgItem(hwndDlg,IDC_STATIC_ABOUT_URL))
  207. {
  208. ::SetTextColor((HDC)wParam, ::GetSysColor (COLOR_HIGHLIGHT));
  209. ::SetBkColor((HDC)wParam, ::GetSysColor (COLOR_BTNFACE));
  210.  
  211. return (LRESULT) hBrushStatic;
  212. }
  213. else
  214. return (LRESULT) NULL;
  215. */
  216. case WM_MOUSEMOVE:
  217. {
  218. POINT pnt;
  219. ::GetCursorPos(&pnt);
  220.  
  221. RECT rect;
  222. ::GetWindowRect( ::GetDlgItem(hwndDlg,IDC_STATIC_ABOUT_URL), &rect);
  223.  
  224. if ( ::PtInRect(&rect,pnt) )
  225. {
  226. ::SetCursor(hcOverCursor);
  227. }
  228.  
  229.  
  230. }
  231. break;
  232.  
  233. case WM_LBUTTONUP:
  234. {
  235. POINT pnt;
  236. ::GetCursorPos(&pnt);
  237.  
  238. RECT rect;
  239. ::GetWindowRect( ::GetDlgItem(hwndDlg,IDC_STATIC_ABOUT_URL), &rect);
  240.  
  241. TCHAR Url[200];
  242. bool bUrl = false;
  243. if (::PtInRect(&rect,pnt))
  244. {
  245. wsprintf(Url,LAME_URL);
  246. bUrl = true;
  247. }
  248.  
  249. if (bUrl)
  250. {
  251. LPSTR tmpStr;
  252. HRESULT hresult = ::TranslateURL(Url, TRANSLATEURL_FL_GUESS_PROTOCOL|TRANSLATEURL_FL_GUESS_PROTOCOL, &tmpStr);
  253. if (hresult == S_OK)
  254. ::ShellExecute(hwndDlg,"open",tmpStr,NULL,"",SW_SHOWMAXIMIZED );
  255. else if (hresult == S_FALSE)
  256. ::ShellExecute(hwndDlg,"open",Url,NULL,"",SW_SHOWMAXIMIZED );
  257. }
  258.  
  259. }
  260. break;
  261.  
  262. case WM_COMMAND:
  263. UINT command;
  264. command = GET_WM_COMMAND_ID(wParam, lParam);
  265. if (IDOK == command)
  266. {
  267. EndDialog(hwndDlg, TRUE);
  268. }
  269. bResult = FALSE;
  270. break;
  271.  
  272. case IDC_STATIC_ABOUT_URL:
  273. break;
  274. default:
  275. bResult = FALSE; // will be treated by DefWindowProc
  276. }
  277. return bResult;
  278. }
  279.  
  280. inline DWORD ACM::About(HWND hParentWindow)
  281. {
  282. my_debug.OutPut(DEBUG_LEVEL_FUNC_START, "ACM : About (Parent Window = 0x%08X)",hParentWindow);
  283.  
  284. DialogBoxParam( my_hModule, MAKEINTRESOURCE(IDD_ABOUT), hParentWindow, ::AboutProc , (LPARAM)this);
  285.  
  286. return DRVCNF_OK; // Can also return
  287. // DRVCNF_CANCEL
  288. // and DRVCNF_RESTART
  289. }
  290.  
  291.  
  292. //////////////////////////////////////////////////////////////////////
  293. // Construction/Destruction
  294. //////////////////////////////////////////////////////////////////////
  295.  
  296. ACM::ACM( HMODULE hModule )
  297. :my_hModule(hModule),
  298. my_hIcon(NULL),
  299. my_debug(ADbg(DEBUG_LEVEL_CREATION)),
  300. my_EncodingProperties(hModule)
  301. {
  302. my_EncodingProperties.ParamsRestore();
  303.  
  304. /// \todo get the debug level from the registry
  305. unsigned char DebugFileName[512];
  306.  
  307. char tmp[128];
  308. wsprintf(tmp,"LAMEacm 0x%08X",this);
  309. my_debug.setPrefix(tmp); /// \todo get it from the registry
  310. my_debug.setIncludeTime(true); /// \todo get it from the registry
  311.  
  312. // Check in the registry if we have to Output Debug information
  313. DebugFileName[0] = '\0';
  314.  
  315. HKEY OssKey;
  316. if (RegOpenKeyEx( HKEY_LOCAL_MACHINE, "SOFTWARE\\MUKOLI", 0, KEY_READ , &OssKey ) == ERROR_SUCCESS) {
  317. DWORD DataType;
  318. DWORD DebugFileNameSize = 512;
  319. if (RegQueryValueEx( OssKey, "DebugFile", NULL, &DataType, DebugFileName, &DebugFileNameSize ) == ERROR_SUCCESS) {
  320. if (DataType == REG_SZ) {
  321. my_debug.setUseFile(true);
  322. my_debug.setDebugFile((char *)DebugFileName);
  323. my_debug.OutPut("Debug file is %s",(char *)DebugFileName);
  324. }
  325. }
  326. }
  327.  
  328. #if LAME_ALPHA_VERSION > 0
  329. wsprintf(VersionString,"%s - %d.%d (alpha %d)", ACM_VERSION, LAME_MAJOR_VERSION, LAME_MINOR_VERSION,LAME_PATCH_VERSION);
  330. #elif LAME_BETA_VERSION > 0
  331. wsprintf(VersionString,"%s - %d.%d (beta %d)", ACM_VERSION, LAME_MAJOR_VERSION, LAME_MINOR_VERSION, LAME_PATCH_VERSION);
  332. #else
  333. wsprintf(VersionString,"%s - %d.%d (stable)", ACM_VERSION, LAME_MAJOR_VERSION, LAME_MINOR_VERSION);
  334. #endif
  335.  
  336. BuildBitrateTable();
  337. my_debug.OutPut(DEBUG_LEVEL_FUNC_START, "New ACM Creation (0x%08X)",this);
  338. }
  339.  
  340. ACM::~ACM()
  341. {
  342. // not used, it's done automatically when closing the driver if (my_hIcon != NULL)
  343. // CloseHandle(my_hIcon);
  344.  
  345. bitrate_table.clear();
  346.  
  347. my_debug.OutPut(DEBUG_LEVEL_FUNC_START, "ACM Deleted (0x%08X)",this);
  348. }
  349.  
  350. //////////////////////////////////////////////////////////////////////
  351. // Main message handler
  352. //////////////////////////////////////////////////////////////////////
  353.  
  354. LONG ACM::DriverProcedure(const HDRVR hdrvr, const UINT msg, LONG lParam1, LONG lParam2)
  355. {
  356. DWORD dwRes = 0L;
  357.  
  358. //my_debug.OutPut(DEBUG_LEVEL_MSG, "message 0x%08X for ThisACM 0x%08X", msg, this);
  359.  
  360. switch (msg) {
  361. case DRV_INSTALL:
  362. my_debug.OutPut(DEBUG_LEVEL_MSG, "DRV_INSTALL");
  363. // Sent when the driver is installed.
  364. dwRes = DRVCNF_OK; // Can also return
  365. break; // DRVCNF_CANCEL
  366. // and DRV_RESTART
  367.  
  368. case DRV_REMOVE:
  369. // Sent when the driver is removed.
  370. my_debug.OutPut(DEBUG_LEVEL_MSG, "DRV_REMOVE");
  371. dwRes = 1L; // return value ignored
  372. break;
  373.  
  374. case DRV_QUERYCONFIGURE:
  375. my_debug.OutPut(DEBUG_LEVEL_MSG, "DRV_QUERYCONFIGURE");
  376. // Sent to determine if the driver can be
  377. // configured.
  378. dwRes = 1L; // Zero indicates configuration
  379. break; // NOT supported
  380.  
  381. case DRV_CONFIGURE:
  382. my_debug.OutPut(DEBUG_LEVEL_MSG, "DRV_CONFIGURE");
  383. // Sent to display the configuration
  384. // dialog box for the driver.
  385. // dwRes = Configure( (HWND) lParam1, (LPDRVCONFIGINFO) lParam2 );
  386. if (my_EncodingProperties.Config(my_hModule, (HWND) lParam1))
  387. {
  388. dwRes = DRVCNF_OK; // Can also return
  389. // DRVCNF_CANCEL
  390. // and DRVCNF_RESTART
  391. } else {
  392. dwRes = DRVCNF_CANCEL;
  393. }
  394. break;
  395.  
  396. /**************************************
  397. // ACM additional messages
  398. ***************************************/
  399.  
  400. case ACMDM_DRIVER_ABOUT:
  401. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_DRIVER_ABOUT");
  402.  
  403. dwRes = About( (HWND) lParam1 );
  404.  
  405. break;
  406.  
  407. case ACMDM_DRIVER_DETAILS: // acmDriverDetails
  408. // Fill-in general informations about the driver/codec
  409. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_DRIVER_DETAILS");
  410.  
  411. dwRes = OnDriverDetails(hdrvr, (LPACMDRIVERDETAILS) lParam1);
  412. break;
  413.  
  414. case ACMDM_FORMATTAG_DETAILS: // acmFormatTagDetails
  415. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_FORMATTAG_DETAILS");
  416.  
  417. dwRes = OnFormatTagDetails((LPACMFORMATTAGDETAILS) lParam1, lParam2);
  418.  
  419. break;
  420.  
  421. case ACMDM_FORMAT_DETAILS: // acmFormatDetails
  422. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_FORMAT_DETAILS");
  423.  
  424. dwRes = OnFormatDetails((LPACMFORMATDETAILS) lParam1, lParam2);
  425. break;
  426.  
  427. case ACMDM_FORMAT_SUGGEST: // acmFormatSuggest
  428. // Sent to determine if the driver can be
  429. // configured.
  430. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_FORMAT_SUGGEST");
  431. dwRes = OnFormatSuggest((LPACMDRVFORMATSUGGEST) lParam1);
  432. break;
  433.  
  434. /**************************************
  435. // ACM stream messages
  436. ***************************************/
  437.  
  438. case ACMDM_STREAM_OPEN:
  439. // Sent to determine if the driver can be
  440. // configured.
  441. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_STREAM_OPEN");
  442. dwRes = OnStreamOpen((LPACMDRVSTREAMINSTANCE) lParam1);
  443. break;
  444.  
  445. case ACMDM_STREAM_SIZE:
  446. // returns a recommended size for a source
  447. // or destination buffer on an ACM stream
  448. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_STREAM_SIZE");
  449. dwRes = OnStreamSize((LPACMDRVSTREAMINSTANCE)lParam1, (LPACMDRVSTREAMSIZE)lParam2);
  450. break;
  451.  
  452. case ACMDM_STREAM_PREPARE:
  453. // prepares an ACMSTREAMHEADER structure for
  454. // an ACM stream conversion
  455. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_STREAM_PREPARE");
  456. dwRes = OnStreamPrepareHeader((LPACMDRVSTREAMINSTANCE)lParam1, (LPACMSTREAMHEADER) lParam2);
  457. break;
  458.  
  459. case ACMDM_STREAM_UNPREPARE:
  460. // cleans up the preparation performed by
  461. // the ACMDM_STREAM_PREPARE message for an ACM stream
  462. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_STREAM_UNPREPARE");
  463. dwRes = OnStreamUnPrepareHeader((LPACMDRVSTREAMINSTANCE)lParam1, (LPACMSTREAMHEADER) lParam2);
  464. break;
  465.  
  466. case ACMDM_STREAM_CONVERT:
  467. // perform a conversion on the specified conversion stream
  468. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_STREAM_CONVERT");
  469. dwRes = OnStreamConvert((LPACMDRVSTREAMINSTANCE)lParam1, (LPACMDRVSTREAMHEADER) lParam2);
  470. break;
  471.  
  472. case ACMDM_STREAM_CLOSE:
  473. // closes an ACM conversion stream
  474. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACMDM_STREAM_CLOSE");
  475. dwRes = OnStreamClose((LPACMDRVSTREAMINSTANCE)lParam1);
  476. break;
  477.  
  478. /**************************************
  479. // Unknown message
  480. ***************************************/
  481.  
  482. default:
  483. // Process any other messages.
  484. my_debug.OutPut(DEBUG_LEVEL_MSG, "ACM::DriverProc unknown message (0x%08X), lParam1 = 0x%08X, lParam2 = 0x%08X", msg, lParam1, lParam2);
  485. return DefDriverProc ((DWORD)this, hdrvr, msg, lParam1, lParam2);
  486. }
  487.  
  488. return dwRes;
  489. }
  490.  
  491. //////////////////////////////////////////////////////////////////////
  492. // Special message handlers
  493. //////////////////////////////////////////////////////////////////////
  494. /*!
  495. Retreive the config details of this ACM driver
  496. The index represent the specified format
  497.  
  498. \param a_FormatDetails will be filled with all the corresponding data
  499. */
  500. inline DWORD ACM::OnFormatDetails(LPACMFORMATDETAILS a_FormatDetails, const LPARAM a_Query)
  501. {
  502. DWORD Result = ACMERR_NOTPOSSIBLE;
  503.  
  504. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "ACM_FORMATDETAILS a_Query = 0x%08X",a_Query);
  505. switch (a_Query & ACM_FORMATDETAILSF_QUERYMASK) {
  506.  
  507. // Fill-in the informations corresponding to the FormatDetails->dwFormatTagIndex
  508. case ACM_FORMATDETAILSF_INDEX :
  509. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "enter ACM_FORMATDETAILSF_INDEX for index 0x%04X:%03d",a_FormatDetails->dwFormatTag,a_FormatDetails->dwFormatIndex);
  510. if (a_FormatDetails->dwFormatTag == PERSONAL_FORMAT) {
  511. if (a_FormatDetails->dwFormatIndex < GetNumberEncodingFormats()) {
  512. LPWAVEFORMATEX WaveExt;
  513. WaveExt = a_FormatDetails->pwfx;
  514.  
  515. WaveExt->wFormatTag = PERSONAL_FORMAT;
  516.  
  517. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "format in : channels %d, sample rate %d", WaveExt->nChannels, WaveExt->nSamplesPerSec);
  518. GetMP3FormatForIndex(a_FormatDetails->dwFormatIndex, *WaveExt, a_FormatDetails->szFormat);
  519. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "format out : channels %d, sample rate %d", WaveExt->nChannels, WaveExt->nSamplesPerSec);
  520. Result = MMSYSERR_NOERROR;
  521. }
  522. else
  523. {
  524. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "ACM_FORMATDETAILSF_INDEX unknown index 0x%04X:%03d",a_FormatDetails->dwFormatTag,a_FormatDetails->dwFormatIndex);
  525. }
  526. }
  527. else if (a_FormatDetails->dwFormatTag == WAVE_FORMAT_PCM) {
  528. if (a_FormatDetails->dwFormatIndex < FORMAT_MAX_NB_PCM) {
  529. LPWAVEFORMATEX WaveExt;
  530. WaveExt = a_FormatDetails->pwfx;
  531.  
  532. WaveExt->wFormatTag = WAVE_FORMAT_PCM;
  533.  
  534. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "format in : channels %d, sample rate %d", WaveExt->nChannels, WaveExt->nSamplesPerSec);
  535. GetPCMFormatForIndex(a_FormatDetails->dwFormatIndex, *WaveExt, a_FormatDetails->szFormat);
  536. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "format out : channels %d, sample rate %d", WaveExt->nChannels, WaveExt->nSamplesPerSec);
  537. Result = MMSYSERR_NOERROR;
  538. }
  539. else
  540. {
  541. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "ACM_FORMATDETAILSF_INDEX unknown index 0x%04X:%03d",a_FormatDetails->dwFormatTag,a_FormatDetails->dwFormatIndex);
  542. }
  543. }
  544. else
  545. {
  546. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Unknown a_FormatDetails->dwFormatTag = 0x%08X",a_FormatDetails->dwFormatTag);
  547. }
  548.  
  549. case ACM_FORMATDETAILSF_FORMAT :
  550. /// \todo we may output the corresponding strong (only for personal format)
  551. LPWAVEFORMATEX WaveExt;
  552. WaveExt = a_FormatDetails->pwfx;
  553.  
  554. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "enter ACM_FORMATDETAILSF_FORMAT : 0x%04X:%03d, format in : channels %d, sample rate %d",a_FormatDetails->dwFormatTag,a_FormatDetails->dwFormatIndex, WaveExt->nChannels, WaveExt->nSamplesPerSec);
  555.  
  556. Result = MMSYSERR_NOERROR;
  557. break;
  558. default:
  559. Result = ACMERR_NOTPOSSIBLE;
  560. break;
  561. }
  562.  
  563. a_FormatDetails->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CODEC;
  564.  
  565. return Result;
  566. }
  567.  
  568. /*!
  569. Retreive the details of each known format by this ACM driver
  570. The index represent the specified format (0 = MP3 / 1 = PCM)
  571.  
  572. \param a_FormatTagDetails will be filled with all the corresponding data
  573. */
  574. inline DWORD ACM::OnFormatTagDetails(LPACMFORMATTAGDETAILS a_FormatTagDetails, const LPARAM a_Query)
  575. {
  576. DWORD Result;
  577. DWORD the_format = WAVE_FORMAT_UNKNOWN; // the format to give details
  578.  
  579. if (a_FormatTagDetails->cbStruct >= sizeof(*a_FormatTagDetails)) {
  580.  
  581. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "ACMDM_FORMATTAG_DETAILS, a_Query = 0x%08X",a_Query);
  582. switch(a_Query & ACM_FORMATTAGDETAILSF_QUERYMASK) {
  583.  
  584. case ACM_FORMATTAGDETAILSF_INDEX:
  585. // Fill-in the informations corresponding to the a_FormatDetails->dwFormatTagIndex
  586. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "get ACM_FORMATTAGDETAILSF_INDEX for index %03d",a_FormatTagDetails->dwFormatTagIndex);
  587.  
  588. if (a_FormatTagDetails->dwFormatTagIndex < FORMAT_TAG_MAX_NB) {
  589. switch (a_FormatTagDetails->dwFormatTagIndex)
  590. {
  591. case 0:
  592. the_format = PERSONAL_FORMAT;
  593. break;
  594. default :
  595. the_format = WAVE_FORMAT_PCM;
  596. break;
  597. }
  598. }
  599. else
  600. {
  601. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "ACM_FORMATTAGDETAILSF_INDEX for unsupported index %03d",a_FormatTagDetails->dwFormatTagIndex);
  602. Result = ACMERR_NOTPOSSIBLE;
  603. }
  604. break;
  605.  
  606. case ACM_FORMATTAGDETAILSF_FORMATTAG:
  607. // Fill-in the informations corresponding to the a_FormatDetails->dwFormatTagIndex and hdrvr given
  608. switch (a_FormatTagDetails->dwFormatTag)
  609. {
  610. case WAVE_FORMAT_PCM:
  611. the_format = WAVE_FORMAT_PCM;
  612. break;
  613. case PERSONAL_FORMAT:
  614. the_format = PERSONAL_FORMAT;
  615. break;
  616. default:
  617. return (ACMERR_NOTPOSSIBLE);
  618. }
  619. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "get ACM_FORMATTAGDETAILSF_FORMATTAG for index 0x%02X, cStandardFormats = %d",a_FormatTagDetails->dwFormatTagIndex,a_FormatTagDetails->cStandardFormats);
  620. break;
  621. case ACM_FORMATTAGDETAILSF_LARGESTSIZE:
  622. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "ACM_FORMATTAGDETAILSF_LARGESTSIZE not used");
  623. Result = 0L;
  624. break;
  625. default:
  626. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "OnFormatTagDetails Unknown Format tag query");
  627. Result = MMSYSERR_NOTSUPPORTED;
  628. break;
  629. }
  630.  
  631. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "OnFormatTagDetails the_format = 0x%08X",the_format);
  632. switch(the_format)
  633. {
  634. case WAVE_FORMAT_PCM:
  635. a_FormatTagDetails->dwFormatTag = WAVE_FORMAT_PCM;
  636. a_FormatTagDetails->dwFormatTagIndex = 0;
  637. a_FormatTagDetails->cbFormatSize = sizeof(PCMWAVEFORMAT);
  638. /// \note 0 may mean we don't know how to decode
  639. a_FormatTagDetails->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CODEC;
  640. a_FormatTagDetails->cStandardFormats = FORMAT_MAX_NB_PCM;
  641. // should be filled by Windows a_FormatTagDetails->szFormatTag[0] = '\0';
  642. Result = MMSYSERR_NOERROR;
  643. break;
  644. case PERSONAL_FORMAT:
  645. a_FormatTagDetails->dwFormatTag = PERSONAL_FORMAT;
  646. a_FormatTagDetails->dwFormatTagIndex = 1;
  647. a_FormatTagDetails->cbFormatSize = SIZE_FORMAT_STRUCT;
  648. a_FormatTagDetails->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CODEC;
  649. a_FormatTagDetails->cStandardFormats = GetNumberEncodingFormats();
  650. lstrcpyW( a_FormatTagDetails->szFormatTag, L"Lame MP3" );
  651. Result = MMSYSERR_NOERROR;
  652. break;
  653. default:
  654. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "OnFormatTagDetails Unknown format 0x%08X",the_format);
  655. return (ACMERR_NOTPOSSIBLE);
  656. }
  657. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "OnFormatTagDetails %d possibilities for format 0x%08X",a_FormatTagDetails->cStandardFormats,the_format);
  658. }
  659. else
  660. {
  661. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "a_FormatTagDetails->cbStruct < sizeof(*a_FormatDetails)");
  662. Result = ACMERR_NOTPOSSIBLE;
  663. }
  664.  
  665. return Result;
  666. }
  667.  
  668. /*!
  669. Retreive the global details of this ACM driver
  670.  
  671. \param a_DriverDetail will be filled with all the corresponding data
  672. */
  673. inline DWORD ACM::OnDriverDetails(const HDRVR hdrvr, LPACMDRIVERDETAILS a_DriverDetail)
  674. {
  675. if (my_hIcon == NULL)
  676. my_hIcon = LoadIcon(GetDriverModuleHandle(hdrvr), MAKEINTRESOURCE(IDI_ICON));
  677. a_DriverDetail->hicon = my_hIcon;
  678.  
  679. a_DriverDetail->fccType = ACMDRIVERDETAILS_FCCTYPE_AUDIOCODEC;
  680. a_DriverDetail->fccComp = ACMDRIVERDETAILS_FCCCOMP_UNDEFINED;
  681.  
  682. /// \note this is an explicit hack of the FhG values
  683. /// \note later it could be a new value when the decoding is done
  684. a_DriverDetail->wMid = MM_FRAUNHOFER_IIS;
  685. a_DriverDetail->wPid = MM_FHGIIS_MPEGLAYER3;
  686.  
  687. a_DriverDetail->vdwACM = VERSION_MSACM;
  688. a_DriverDetail->vdwDriver = VERSION_ACM_DRIVER;
  689. a_DriverDetail->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CODEC;
  690. a_DriverDetail->cFormatTags = FORMAT_TAG_MAX_NB; // 2 : MP3 and PCM
  691. // a_DriverDetail->cFormatTags = 1; // 2 : MP3 and PCM
  692. a_DriverDetail->cFilterTags = FILTER_TAG_MAX_NB;
  693.  
  694. lstrcpyW( a_DriverDetail->szShortName, L"LAME MP3" );
  695. char tmpStr[128];
  696. wsprintf(tmpStr, "LAME MP3 Codec v%s", GetVersionString());
  697. int u = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, tmpStr, -1, a_DriverDetail->szLongName, 0);
  698. MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, tmpStr, -1, a_DriverDetail->szLongName, u);
  699. lstrcpyW( a_DriverDetail->szCopyright, L"2002 Steve Lhomme" );
  700. lstrcpyW( a_DriverDetail->szLicensing, L"LGPL (see gnu.org)" );
  701. /// \todo update this part when the code changes
  702. lstrcpyW( a_DriverDetail->szFeatures , L"only CBR implementation" );
  703.  
  704. return MMSYSERR_NOERROR; // Can also return DRVCNF_CANCEL
  705. }
  706.  
  707. /*!
  708. Suggest an output format for the specified input format
  709.  
  710. \param a_FormatSuggest will be filled with all the corresponding data
  711. */
  712. inline DWORD ACM::OnFormatSuggest(LPACMDRVFORMATSUGGEST a_FormatSuggest)
  713. {
  714. DWORD Result = MMSYSERR_NOTSUPPORTED;
  715. DWORD fdwSuggest = (ACM_FORMATSUGGESTF_TYPEMASK & a_FormatSuggest->fdwSuggest);
  716.  
  717. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggest %s%s%s%s (0x%08X)",
  718. (fdwSuggest & ACM_FORMATSUGGESTF_NCHANNELS) ? "channels, ":"",
  719. (fdwSuggest & ACM_FORMATSUGGESTF_NSAMPLESPERSEC) ? "samples/sec, ":"",
  720. (fdwSuggest & ACM_FORMATSUGGESTF_WBITSPERSAMPLE) ? "bits/sample, ":"",
  721. (fdwSuggest & ACM_FORMATSUGGESTF_WFORMATTAG) ? "format, ":"",
  722. fdwSuggest);
  723.  
  724. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggest for source format = 0x%04X, channels = %d, Samples/s = %d, AvgB/s = %d, BlockAlign = %d, b/sample = %d",
  725. a_FormatSuggest->pwfxSrc->wFormatTag,
  726. a_FormatSuggest->pwfxSrc->nChannels,
  727. a_FormatSuggest->pwfxSrc->nSamplesPerSec,
  728. a_FormatSuggest->pwfxSrc->nAvgBytesPerSec,
  729. a_FormatSuggest->pwfxSrc->nBlockAlign,
  730. a_FormatSuggest->pwfxSrc->wBitsPerSample);
  731.  
  732. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggested destination format = 0x%04X, channels = %d, Samples/s = %d, AvgB/s = %d, BlockAlign = %d, b/sample = %d",
  733. a_FormatSuggest->pwfxDst->wFormatTag,
  734. a_FormatSuggest->pwfxDst->nChannels,
  735. a_FormatSuggest->pwfxDst->nSamplesPerSec,
  736. a_FormatSuggest->pwfxDst->nAvgBytesPerSec,
  737. a_FormatSuggest->pwfxDst->nBlockAlign,
  738. a_FormatSuggest->pwfxDst->wBitsPerSample);
  739.  
  740. switch (a_FormatSuggest->pwfxSrc->wFormatTag)
  741. {
  742. case WAVE_FORMAT_PCM:
  743. /// \todo handle here the decoding ?
  744. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggest for PCM source");
  745. //
  746. // if the destination format tag is restricted, verify that
  747. // it is within our capabilities...
  748. //
  749. // this driver is able to decode to PCM
  750. //
  751. if (ACM_FORMATSUGGESTF_WFORMATTAG & fdwSuggest)
  752. {
  753. if (PERSONAL_FORMAT != a_FormatSuggest->pwfxDst->wFormatTag)
  754. return (ACMERR_NOTPOSSIBLE);
  755. }
  756. else
  757. {
  758. a_FormatSuggest->pwfxDst->wFormatTag = PERSONAL_FORMAT;
  759. }
  760.  
  761.  
  762. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggest succeed A");
  763. //
  764. // if the destination channel count is restricted, verify that
  765. // it is within our capabilities...
  766. //
  767. // this driver is not able to change the number of channels
  768. //
  769. if (ACM_FORMATSUGGESTF_NCHANNELS & fdwSuggest)
  770. {
  771. if (a_FormatSuggest->pwfxSrc->nChannels != a_FormatSuggest->pwfxDst->nChannels)
  772. return (ACMERR_NOTPOSSIBLE);
  773. }
  774. else
  775. {
  776. a_FormatSuggest->pwfxDst->nChannels = a_FormatSuggest->pwfxSrc->nChannels;
  777. }
  778.  
  779. if (a_FormatSuggest->pwfxSrc->nChannels != 1 && a_FormatSuggest->pwfxSrc->nChannels != 2)
  780. return MMSYSERR_INVALPARAM;
  781.  
  782.  
  783. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggest succeed B");
  784. //
  785. // if the destination samples per second is restricted, verify
  786. // that it is within our capabilities...
  787. //
  788. // this driver is not able to change the sample rate
  789. //
  790. if (ACM_FORMATSUGGESTF_NSAMPLESPERSEC & fdwSuggest)
  791. {
  792. if (a_FormatSuggest->pwfxSrc->nSamplesPerSec != a_FormatSuggest->pwfxDst->nSamplesPerSec)
  793. return (ACMERR_NOTPOSSIBLE);
  794. }
  795. else
  796. {
  797. a_FormatSuggest->pwfxDst->nSamplesPerSec = a_FormatSuggest->pwfxSrc->nSamplesPerSec;
  798. }
  799.  
  800.  
  801. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggest succeed C");
  802. //
  803. // if the destination bits per sample is restricted, verify
  804. // that it is within our capabilities...
  805. //
  806. // We prefer decoding to 16-bit PCM.
  807. //
  808. if (ACM_FORMATSUGGESTF_WBITSPERSAMPLE & fdwSuggest)
  809. {
  810. if ( (16 != a_FormatSuggest->pwfxDst->wBitsPerSample) && (8 != a_FormatSuggest->pwfxDst->wBitsPerSample) )
  811. return (ACMERR_NOTPOSSIBLE);
  812. }
  813. else
  814. {
  815. a_FormatSuggest->pwfxDst->wBitsPerSample = 16;
  816. }
  817.  
  818. // a_FormatSuggest->pwfxDst->nBlockAlign = FORMAT_BLOCK_ALIGN;
  819. a_FormatSuggest->pwfxDst->nBlockAlign = a_FormatSuggest->pwfxDst->nChannels * a_FormatSuggest->pwfxDst->wBitsPerSample / 8;
  820. a_FormatSuggest->pwfxDst->nAvgBytesPerSec = a_FormatSuggest->pwfxDst->nChannels * 64000 / 8;
  821.  
  822. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggest succeed");
  823. Result = MMSYSERR_NOERROR;
  824.  
  825.  
  826. break;
  827. case PERSONAL_FORMAT:
  828. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggest for PERSONAL source");
  829. //
  830. // if the destination format tag is restricted, verify that
  831. // it is within our capabilities...
  832. //
  833. // this driver is able to decode to PCM
  834. //
  835. if (ACM_FORMATSUGGESTF_WFORMATTAG & fdwSuggest)
  836. {
  837. if (WAVE_FORMAT_PCM != a_FormatSuggest->pwfxDst->wFormatTag)
  838. return (ACMERR_NOTPOSSIBLE);
  839. }
  840. else
  841. {
  842. a_FormatSuggest->pwfxDst->wFormatTag = WAVE_FORMAT_PCM;
  843. }
  844.  
  845.  
  846. //
  847. // if the destination channel count is restricted, verify that
  848. // it is within our capabilities...
  849. //
  850. // this driver is not able to change the number of channels
  851. //
  852. if (ACM_FORMATSUGGESTF_NCHANNELS & fdwSuggest)
  853. {
  854. if (a_FormatSuggest->pwfxSrc->nChannels != a_FormatSuggest->pwfxDst->nChannels)
  855. return (ACMERR_NOTPOSSIBLE);
  856. }
  857. else
  858. {
  859. a_FormatSuggest->pwfxDst->nChannels = a_FormatSuggest->pwfxSrc->nChannels;
  860. }
  861.  
  862.  
  863. //
  864. // if the destination samples per second is restricted, verify
  865. // that it is within our capabilities...
  866. //
  867. // this driver is not able to change the sample rate
  868. //
  869. if (ACM_FORMATSUGGESTF_NSAMPLESPERSEC & fdwSuggest)
  870. {
  871. if (a_FormatSuggest->pwfxSrc->nSamplesPerSec != a_FormatSuggest->pwfxDst->nSamplesPerSec)
  872. return (ACMERR_NOTPOSSIBLE);
  873. }
  874. else
  875. {
  876. a_FormatSuggest->pwfxDst->nSamplesPerSec = a_FormatSuggest->pwfxSrc->nSamplesPerSec;
  877. }
  878.  
  879.  
  880. //
  881. // if the destination bits per sample is restricted, verify
  882. // that it is within our capabilities...
  883. //
  884. // We prefer decoding to 16-bit PCM.
  885. //
  886. if (ACM_FORMATSUGGESTF_WBITSPERSAMPLE & fdwSuggest)
  887. {
  888. if ( (16 != a_FormatSuggest->pwfxDst->wBitsPerSample) && (8 != a_FormatSuggest->pwfxDst->wBitsPerSample) )
  889. return (ACMERR_NOTPOSSIBLE);
  890. }
  891. else
  892. {
  893. a_FormatSuggest->pwfxDst->wBitsPerSample = 16;
  894. }
  895.  
  896. // a_FormatSuggest->pwfxDst->nBlockAlign = FORMAT_BLOCK_ALIGN;
  897. a_FormatSuggest->pwfxDst->nBlockAlign = a_FormatSuggest->pwfxDst->nChannels * a_FormatSuggest->pwfxDst->wBitsPerSample / 8;
  898. /// \todo this value must be a correct one !
  899. a_FormatSuggest->pwfxDst->nAvgBytesPerSec = a_FormatSuggest->pwfxDst->nSamplesPerSec * a_FormatSuggest->pwfxDst->nChannels * a_FormatSuggest->pwfxDst->wBitsPerSample / 8;
  900.  
  901. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggest succeed");
  902. Result = MMSYSERR_NOERROR;
  903.  
  904.  
  905. break;
  906. }
  907.  
  908. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Suggested destination format = 0x%04X, channels = %d, Samples/s = %d, AvgB/s = %d, BlockAlign = %d, b/sample = %d",
  909. a_FormatSuggest->pwfxDst->wFormatTag,
  910. a_FormatSuggest->pwfxDst->nChannels,
  911. a_FormatSuggest->pwfxDst->nSamplesPerSec,
  912. a_FormatSuggest->pwfxDst->nAvgBytesPerSec,
  913. a_FormatSuggest->pwfxDst->nBlockAlign,
  914. a_FormatSuggest->pwfxDst->wBitsPerSample);
  915.  
  916. return Result;
  917. }
  918.  
  919. /*!
  920. Create a stream instance for decoding/encoding
  921.  
  922. \param a_StreamInstance contain information about the stream desired
  923. */
  924. inline DWORD ACM::OnStreamOpen(LPACMDRVSTREAMINSTANCE a_StreamInstance)
  925. {
  926. DWORD Result = ACMERR_NOTPOSSIBLE;
  927.  
  928. //
  929. // the most important condition to check before doing anything else
  930. // is that this ACM driver can actually perform the conversion we are
  931. // being opened for. this check should fail as quickly as possible
  932. // if the conversion is not possible by this driver.
  933. //
  934. // it is VERY important to fail quickly so the ACM can attempt to
  935. // find a driver that is suitable for the conversion. also note that
  936. // the ACM may call this driver several times with slightly different
  937. // format specifications before giving up.
  938. //
  939. // this driver first verifies that the source and destination formats
  940. // are acceptable...
  941. //
  942. switch (a_StreamInstance->pwfxSrc->wFormatTag)
  943. {
  944. case WAVE_FORMAT_PCM:
  945. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Open stream for PCM source (%05d samples %d channels %d bits/sample)",a_StreamInstance->pwfxSrc->nSamplesPerSec,a_StreamInstance->pwfxSrc->nChannels,a_StreamInstance->pwfxSrc->wBitsPerSample);
  946. if (a_StreamInstance->pwfxDst->wFormatTag == PERSONAL_FORMAT)
  947. {
  948. unsigned int OutputFrequency;
  949.  
  950. /// \todo Smart mode
  951. if (my_EncodingProperties.GetSmartOutputMode())
  952. OutputFrequency = ACMStream::GetOutputSampleRate(a_StreamInstance->pwfxSrc->nSamplesPerSec,a_StreamInstance->pwfxDst->nAvgBytesPerSec,a_StreamInstance->pwfxDst->nChannels);
  953. else
  954. OutputFrequency = a_StreamInstance->pwfxSrc->nSamplesPerSec;
  955.  
  956. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Open stream for PERSONAL output (%05d samples %d channels %d bits/sample %d kbps)",a_StreamInstance->pwfxDst->nSamplesPerSec,a_StreamInstance->pwfxDst->nChannels,a_StreamInstance->pwfxDst->wBitsPerSample,8 * a_StreamInstance->pwfxDst->nAvgBytesPerSec);
  957.  
  958. /// \todo add the possibility to have channel resampling (mono to stereo / stereo to mono)
  959. /// \todo support resampling ?
  960. /// \todo only do the test on OutputFrequency in "Smart Output" mode
  961. if (a_StreamInstance->pwfxDst->nSamplesPerSec != OutputFrequency ||
  962. // a_StreamInstance->pwfxSrc->nSamplesPerSec != a_StreamInstance->pwfxDst->nSamplesPerSec ||
  963. a_StreamInstance->pwfxSrc->nChannels != a_StreamInstance->pwfxDst->nChannels ||
  964. a_StreamInstance->pwfxSrc->wBitsPerSample != 16)
  965. {
  966. Result = ACMERR_NOTPOSSIBLE;
  967. } else {
  968. if ((a_StreamInstance->fdwOpen & ACM_STREAMOPENF_QUERY) == 0)
  969. {
  970. ACMStream * the_stream = ACMStream::Create();
  971. a_StreamInstance->dwInstance = (DWORD) the_stream;
  972.  
  973. if (the_stream != NULL)
  974. {
  975. MPEGLAYER3WAVEFORMAT * casted = (MPEGLAYER3WAVEFORMAT *) a_StreamInstance->pwfxDst;
  976. vbr_mode a_mode = (casted->fdwFlags-2 == 0)?vbr_abr:vbr_off;
  977. if (the_stream->init(a_StreamInstance->pwfxDst->nSamplesPerSec,
  978. OutputFrequency,
  979. a_StreamInstance->pwfxDst->nChannels,
  980. a_StreamInstance->pwfxDst->nAvgBytesPerSec,
  981. a_mode))
  982. Result = MMSYSERR_NOERROR;
  983. else
  984. ACMStream::Erase( the_stream );
  985. }
  986. }
  987. else
  988. {
  989. Result = MMSYSERR_NOERROR;
  990. }
  991. }
  992. }
  993. break;
  994. case PERSONAL_FORMAT:
  995. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Open stream for PERSONAL source (%05d samples %d channels %d bits/sample %d kbps)",a_StreamInstance->pwfxSrc->nSamplesPerSec,a_StreamInstance->pwfxSrc->nChannels,a_StreamInstance->pwfxSrc->wBitsPerSample,8 * a_StreamInstance->pwfxSrc->nAvgBytesPerSec);
  996. if (a_StreamInstance->pwfxDst->wFormatTag == WAVE_FORMAT_PCM)
  997. {
  998. #ifdef ENABLE_DECODING
  999. if ((a_StreamInstance->fdwOpen & ACM_STREAMOPENF_QUERY) == 0)
  1000. {
  1001. /// \todo create the decoding stream
  1002. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Open stream for PCM output (%05d samples %d channels %d bits/sample %d B/s)",a_StreamInstance->pwfxDst->nSamplesPerSec,a_StreamInstance->pwfxDst->nChannels,a_StreamInstance->pwfxDst->wBitsPerSample,a_StreamInstance->pwfxDst->nAvgBytesPerSec);
  1003.  
  1004. DecodeStream * the_stream = DecodeStream::Create();
  1005. a_StreamInstance->dwInstance = (DWORD) the_stream;
  1006.  
  1007. if (the_stream != NULL)
  1008. {
  1009. if (the_stream->init(a_StreamInstance->pwfxDst->nSamplesPerSec,
  1010. a_StreamInstance->pwfxDst->nChannels,
  1011. a_StreamInstance->pwfxDst->nAvgBytesPerSec,
  1012. a_StreamInstance->pwfxSrc->nAvgBytesPerSec))
  1013. Result = MMSYSERR_NOERROR;
  1014. else
  1015. DecodeStream::Erase( the_stream );
  1016. }
  1017. }
  1018. else
  1019. {
  1020. /// \todo decoding verification
  1021. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Open stream is valid");
  1022. Result = MMSYSERR_NOERROR;
  1023. }
  1024. #endif // ENABLE_DECODING
  1025. }
  1026. break;
  1027. }
  1028.  
  1029. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Open stream Result = %d",Result);
  1030. return Result;
  1031. }
  1032.  
  1033. inline DWORD ACM::OnStreamSize(LPACMDRVSTREAMINSTANCE a_StreamInstance, LPACMDRVSTREAMSIZE the_StreamSize)
  1034. {
  1035. DWORD Result = ACMERR_NOTPOSSIBLE;
  1036.  
  1037. switch (ACM_STREAMSIZEF_QUERYMASK & the_StreamSize->fdwSize)
  1038. {
  1039. case ACM_STREAMSIZEF_DESTINATION:
  1040. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Get source buffer size for destination size = %d",the_StreamSize->cbDstLength);
  1041. break;
  1042. case ACM_STREAMSIZEF_SOURCE:
  1043. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "Get destination buffer size for source size = %d",the_StreamSize->cbSrcLength);
  1044. if (WAVE_FORMAT_PCM == a_StreamInstance->pwfxSrc->wFormatTag &&
  1045. PERSONAL_FORMAT == a_StreamInstance->pwfxDst->wFormatTag)
  1046. {
  1047. ACMStream * the_stream = (ACMStream *) a_StreamInstance->dwInstance;
  1048. if (the_stream != NULL)
  1049. {
  1050. the_StreamSize->cbDstLength = the_stream->GetOutputSizeForInput(the_StreamSize->cbSrcLength);
  1051. Result = MMSYSERR_NOERROR;
  1052. }
  1053. }
  1054. else if (PERSONAL_FORMAT == a_StreamInstance->pwfxSrc->wFormatTag &&
  1055. WAVE_FORMAT_PCM== a_StreamInstance->pwfxDst->wFormatTag)
  1056. {
  1057. #ifdef ENABLE_DECODING
  1058. DecodeStream * the_stream = (DecodeStream *) a_StreamInstance->dwInstance;
  1059. if (the_stream != NULL)
  1060. {
  1061. the_StreamSize->cbDstLength = the_stream->GetOutputSizeForInput(the_StreamSize->cbSrcLength);
  1062. Result = MMSYSERR_NOERROR;
  1063. }
  1064. #endif // ENABLE_DECODING
  1065. }
  1066. break;
  1067. default:
  1068. Result = MMSYSERR_INVALFLAG;
  1069. break;
  1070. }
  1071.  
  1072. return Result;
  1073. }
  1074.  
  1075. inline DWORD ACM::OnStreamClose(LPACMDRVSTREAMINSTANCE a_StreamInstance)
  1076. {
  1077. DWORD Result = ACMERR_NOTPOSSIBLE;
  1078.  
  1079. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "OnStreamClose the stream 0x%X",a_StreamInstance->dwInstance);
  1080. if (WAVE_FORMAT_PCM == a_StreamInstance->pwfxSrc->wFormatTag &&
  1081. PERSONAL_FORMAT == a_StreamInstance->pwfxDst->wFormatTag)
  1082. {
  1083. ACMStream::Erase( (ACMStream *) a_StreamInstance->dwInstance );
  1084. }
  1085. else if (PERSONAL_FORMAT == a_StreamInstance->pwfxSrc->wFormatTag &&
  1086. WAVE_FORMAT_PCM== a_StreamInstance->pwfxDst->wFormatTag)
  1087. {
  1088. #ifdef ENABLE_DECODING
  1089. DecodeStream::Erase( (DecodeStream *) a_StreamInstance->dwInstance );
  1090. #endif // ENABLE_DECODING
  1091. }
  1092.  
  1093. // nothing to do yet
  1094. Result = MMSYSERR_NOERROR;
  1095.  
  1096. return Result;
  1097. }
  1098.  
  1099. inline DWORD ACM::OnStreamPrepareHeader(LPACMDRVSTREAMINSTANCE a_StreamInstance, LPACMSTREAMHEADER a_StreamHeader)
  1100. {
  1101. DWORD Result = ACMERR_NOTPOSSIBLE;
  1102.  
  1103. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, " prepare : Src : %d (0x%08X) / %d - Dst : %d (0x%08X) / %d"
  1104. , a_StreamHeader->cbSrcLength
  1105. , a_StreamHeader->pbSrc
  1106. , a_StreamHeader->cbSrcLengthUsed
  1107. , a_StreamHeader->cbDstLength
  1108. , a_StreamHeader->pbDst
  1109. , a_StreamHeader->cbDstLengthUsed
  1110. );
  1111.  
  1112. if (WAVE_FORMAT_PCM == a_StreamInstance->pwfxSrc->wFormatTag &&
  1113. PERSONAL_FORMAT == a_StreamInstance->pwfxDst->wFormatTag)
  1114. {
  1115. ACMStream * the_stream = (ACMStream *)a_StreamInstance->dwInstance;
  1116. if (the_stream->open(my_EncodingProperties))
  1117. Result = MMSYSERR_NOERROR;
  1118. }
  1119. else if (PERSONAL_FORMAT == a_StreamInstance->pwfxSrc->wFormatTag &&
  1120. WAVE_FORMAT_PCM == a_StreamInstance->pwfxDst->wFormatTag)
  1121. {
  1122. #ifdef ENABLE_DECODING
  1123. DecodeStream * the_stream = (DecodeStream *)a_StreamInstance->dwInstance;
  1124. if (the_stream->open())
  1125. Result = MMSYSERR_NOERROR;
  1126. #endif // ENABLE_DECODING
  1127. }
  1128.  
  1129. return Result;
  1130. }
  1131.  
  1132. inline DWORD ACM::OnStreamUnPrepareHeader(LPACMDRVSTREAMINSTANCE a_StreamInstance, LPACMSTREAMHEADER a_StreamHeader)
  1133. {
  1134. DWORD Result = ACMERR_NOTPOSSIBLE;
  1135.  
  1136. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "unprepare : Src : %d / %d - Dst : %d / %d"
  1137. , a_StreamHeader->cbSrcLength
  1138. , a_StreamHeader->cbSrcLengthUsed
  1139. , a_StreamHeader->cbDstLength
  1140. , a_StreamHeader->cbDstLengthUsed
  1141. );
  1142. if (WAVE_FORMAT_PCM == a_StreamInstance->pwfxSrc->wFormatTag &&
  1143. PERSONAL_FORMAT == a_StreamInstance->pwfxDst->wFormatTag)
  1144. {
  1145. ACMStream * the_stream = (ACMStream *)a_StreamInstance->dwInstance;
  1146. DWORD OutputSize = a_StreamHeader->cbDstLength;
  1147. if (the_stream->close(a_StreamHeader->pbDst, &OutputSize) && (OutputSize <= a_StreamHeader->cbDstLength))
  1148. {
  1149. a_StreamHeader->cbDstLengthUsed = OutputSize;
  1150. Result = MMSYSERR_NOERROR;
  1151. }
  1152. }
  1153. else if (PERSONAL_FORMAT == a_StreamInstance->pwfxSrc->wFormatTag &&
  1154. WAVE_FORMAT_PCM== a_StreamInstance->pwfxDst->wFormatTag)
  1155. {
  1156. #ifdef ENABLE_DECODING
  1157. DecodeStream * the_stream = (DecodeStream *)a_StreamInstance->dwInstance;
  1158. DWORD OutputSize = a_StreamHeader->cbDstLength;
  1159. if (the_stream->close(a_StreamHeader->pbDst, &OutputSize) && (OutputSize <= a_StreamHeader->cbDstLength))
  1160. {
  1161. a_StreamHeader->cbDstLengthUsed = OutputSize;
  1162. Result = MMSYSERR_NOERROR;
  1163. }
  1164. #endif // ENABLE_DECODING
  1165. }
  1166.  
  1167. return Result;
  1168. }
  1169.  
  1170. inline DWORD ACM::OnStreamConvert(LPACMDRVSTREAMINSTANCE a_StreamInstance, LPACMDRVSTREAMHEADER a_StreamHeader)
  1171. {
  1172. DWORD Result = ACMERR_NOTPOSSIBLE;
  1173.  
  1174. if (WAVE_FORMAT_PCM == a_StreamInstance->pwfxSrc->wFormatTag &&
  1175. PERSONAL_FORMAT == a_StreamInstance->pwfxDst->wFormatTag)
  1176. {
  1177. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "OnStreamConvert SRC = PCM (encode)");
  1178.  
  1179. ACMStream * the_stream = (ACMStream *) a_StreamInstance->dwInstance;
  1180. if (the_stream != NULL)
  1181. {
  1182. if (the_stream->ConvertBuffer( a_StreamHeader ))
  1183. Result = MMSYSERR_NOERROR;
  1184. }
  1185. }
  1186. else if (PERSONAL_FORMAT == a_StreamInstance->pwfxSrc->wFormatTag &&
  1187. WAVE_FORMAT_PCM == a_StreamInstance->pwfxDst->wFormatTag)
  1188. {
  1189. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "OnStreamConvert SRC = MP3 (decode)");
  1190.  
  1191. #ifdef ENABLE_DECODING
  1192. DecodeStream * the_stream = (DecodeStream *) a_StreamInstance->dwInstance;
  1193. if (the_stream != NULL)
  1194. {
  1195. if (the_stream->ConvertBuffer( a_StreamHeader ))
  1196. Result = MMSYSERR_NOERROR;
  1197. }
  1198. #endif // ENABLE_DECODING
  1199. }
  1200. else
  1201. my_debug.OutPut(DEBUG_LEVEL_FUNC_CODE, "OnStreamConvert unsupported conversion");
  1202.  
  1203. return Result;
  1204. }
  1205.  
  1206.  
  1207. void ACM::GetMP3FormatForIndex(const DWORD the_Index, WAVEFORMATEX & the_Format, unsigned short the_String[ACMFORMATDETAILS_FORMAT_CHARS]) const
  1208. {
  1209. int Block_size;
  1210. char temp[ACMFORMATDETAILS_FORMAT_CHARS];
  1211.  
  1212.  
  1213. if (the_Index < bitrate_table.size())
  1214. {
  1215. // the_Format.wBitsPerSample = 16;
  1216. the_Format.wBitsPerSample = 0;
  1217. /// \todo handle more channel modes (mono, stereo, joint-stereo, dual-channel)
  1218. // the_Format.nChannels = SIZE_CHANNEL_MODE - int(the_Index % SIZE_CHANNEL_MODE);
  1219. the_Format.nBlockAlign = 1;
  1220.  
  1221. the_Format.nSamplesPerSec = bitrate_table[the_Index].frequency;
  1222. the_Format.nAvgBytesPerSec = bitrate_table[the_Index].bitrate * 1000 / 8;
  1223. if (bitrate_table[the_Index].frequency >= mpeg1_freq[SIZE_FREQ_MPEG1-1])
  1224. Block_size = 1152;
  1225. else
  1226. Block_size = 576;
  1227. the_Format.nChannels = bitrate_table[the_Index].channels;
  1228.  
  1229. the_Format.cbSize = sizeof(MPEGLAYER3WAVEFORMAT) - sizeof(WAVEFORMATEX);
  1230. MPEGLAYER3WAVEFORMAT * tmpFormat = (MPEGLAYER3WAVEFORMAT *) &the_Format;
  1231. tmpFormat->wID = 1;
  1232. // this is the only way I found to know if we do CBR or ABR
  1233. tmpFormat->fdwFlags = 2 + ((bitrate_table[the_Index].mode == vbr_abr)?0:2);
  1234. tmpFormat->nBlockSize = Block_size * the_Format.nAvgBytesPerSec / the_Format.nSamplesPerSec;
  1235. tmpFormat->nFramesPerBlock = 1;
  1236. tmpFormat->nCodecDelay = 0; // 0x0571 on FHG
  1237. /// \todo : generate the string with the appropriate stereo mode
  1238. if (bitrate_table[the_Index].mode == vbr_abr)
  1239. wsprintfA( temp, "%d Hz, %d kbps ABR, %s", the_Format.nSamplesPerSec, the_Format.nAvgBytesPerSec * 8 / 1000, (the_Format.nChannels == 1)?"Mono":"Stereo");
  1240. else
  1241. wsprintfA( temp, "%d Hz, %d kbps CBR, %s", the_Format.nSamplesPerSec, the_Format.nAvgBytesPerSec * 8 / 1000, (the_Format.nChannels == 1)?"Mono":"Stereo");
  1242.  
  1243. MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, temp, -1, the_String, ACMFORMATDETAILS_FORMAT_CHARS);
  1244. }
  1245. }
  1246.  
  1247. void ACM::GetPCMFormatForIndex(const DWORD the_Index, WAVEFORMATEX & the_Format, unsigned short the_String[ACMFORMATDETAILS_FORMAT_CHARS]) const
  1248. {
  1249. the_Format.nChannels = SIZE_CHANNEL_MODE - int(the_Index % SIZE_CHANNEL_MODE);
  1250. the_Format.wBitsPerSample = 16;
  1251. the_Format.nBlockAlign = the_Format.nChannels * the_Format.wBitsPerSample / 8;
  1252.  
  1253.  
  1254. DWORD a_Channel_Independent = the_Index / SIZE_CHANNEL_MODE;
  1255.  
  1256. // first MPEG1 frequencies
  1257. if (a_Channel_Independent < SIZE_FREQ_MPEG1)
  1258. {
  1259. the_Format.nSamplesPerSec = mpeg1_freq[a_Channel_Independent];
  1260. }
  1261. else
  1262. {
  1263. a_Channel_Independent -= SIZE_FREQ_MPEG1;
  1264. the_Format.nSamplesPerSec = mpeg2_freq[a_Channel_Independent];
  1265. }
  1266.  
  1267. the_Format.nAvgBytesPerSec = the_Format.nSamplesPerSec * the_Format.nChannels * the_Format.wBitsPerSample / 8;
  1268. }
  1269.  
  1270. DWORD ACM::GetNumberEncodingFormats() const
  1271. {
  1272. return bitrate_table.size();
  1273. }
  1274.  
  1275. bool ACM::IsSmartOutput(const int frequency, const int bitrate, const int channels) const
  1276. {
  1277. double compression_ratio = double(frequency * 2 * channels) / double(bitrate * 100);
  1278.  
  1279. //my_debug.OutPut(DEBUG_LEVEL_FUNC_DEBUG, "compression_ratio %f, freq %d, bitrate %d, channels %d", compression_ratio, frequency, bitrate, channels);
  1280.  
  1281. if(my_EncodingProperties.GetSmartOutputMode())
  1282. return (compression_ratio <= my_EncodingProperties.GetSmartRatio());
  1283. else return true;
  1284. }
  1285.  
  1286. void ACM::BuildBitrateTable()
  1287. {
  1288. my_debug.OutPut("entering BuildBitrateTable");
  1289.  
  1290. // fill the table
  1291. unsigned int channel,bitrate,freq;
  1292. bitrate_table.clear();
  1293.  
  1294. // CBR bitrates
  1295. for (channel = 0;channel < SIZE_CHANNEL_MODE;channel++)
  1296. {
  1297. // MPEG I
  1298. for (freq = 0;freq < SIZE_FREQ_MPEG1;freq++)
  1299. {
  1300. for (bitrate = 0;bitrate < SIZE_BITRATE_MPEG1;bitrate++)
  1301. {
  1302.  
  1303. if (!my_EncodingProperties.GetSmartOutputMode() || IsSmartOutput(mpeg1_freq[freq], mpeg1_bitrate[bitrate], channel+1))
  1304. {
  1305. bitrate_item * bitrate_table_tmp = new bitrate_item;
  1306. if (bitrate_table_tmp == NULL)
  1307. return;
  1308. bitrate_table_tmp->frequency = mpeg1_freq[freq];
  1309. bitrate_table_tmp->bitrate = mpeg1_bitrate[bitrate];
  1310. bitrate_table_tmp->channels = channel+1;
  1311. bitrate_table_tmp->mode = vbr_off;
  1312. bitrate_table.push_back(*bitrate_table_tmp);
  1313. }
  1314. }
  1315. }
  1316. // MPEG II / II.5
  1317. for (freq = 0;freq < SIZE_FREQ_MPEG2;freq++)
  1318. {
  1319. for (bitrate = 0;bitrate < SIZE_BITRATE_MPEG2;bitrate++)
  1320. {
  1321. if (!my_EncodingProperties.GetSmartOutputMode() || IsSmartOutput(mpeg2_freq[freq], mpeg2_bitrate[bitrate], channel+1))
  1322. {
  1323. bitrate_item * bitrate_table_tmp = new bitrate_item;
  1324. if (bitrate_table_tmp == NULL)
  1325. return;
  1326. bitrate_table_tmp->frequency = mpeg2_freq[freq];
  1327. bitrate_table_tmp->bitrate = mpeg2_bitrate[bitrate];
  1328. bitrate_table_tmp->channels = channel+1;
  1329. bitrate_table_tmp->mode = vbr_abr;
  1330. bitrate_table.push_back(*bitrate_table_tmp);
  1331. }
  1332. }
  1333. }
  1334. }
  1335.  
  1336. if (my_EncodingProperties.GetAbrOutputMode())
  1337. // ABR bitrates
  1338. {
  1339. for (channel = 0;channel < SIZE_CHANNEL_MODE;channel++)
  1340. {
  1341. // MPEG I
  1342. for (freq = 0;freq < SIZE_FREQ_MPEG1;freq++)
  1343. {
  1344. for (bitrate = my_EncodingProperties.GetAbrBitrateMax();
  1345. bitrate >= my_EncodingProperties.GetAbrBitrateMin();
  1346. bitrate -= my_EncodingProperties.GetAbrBitrateStep())
  1347. {
  1348. if (bitrate >= mpeg1_bitrate[SIZE_BITRATE_MPEG1-1] && (!my_EncodingProperties.GetSmartOutputMode() || IsSmartOutput(mpeg1_freq[freq], bitrate, channel+1)))
  1349. {
  1350. bitrate_item * bitrate_table_tmp = new bitrate_item;
  1351. if (bitrate_table_tmp == NULL)
  1352. return;
  1353. bitrate_table_tmp->frequency = mpeg1_freq[freq];
  1354. bitrate_table_tmp->bitrate = bitrate;
  1355. bitrate_table_tmp->channels = channel+1;
  1356. bitrate_table_tmp->mode = vbr_abr;
  1357. bitrate_table.push_back(*bitrate_table_tmp);
  1358. }
  1359. }
  1360. }
  1361. // MPEG II / II.5
  1362. for (freq = 0;freq < SIZE_FREQ_MPEG2;freq++)
  1363. {
  1364. for (bitrate = my_EncodingProperties.GetAbrBitrateMax();
  1365. bitrate >= my_EncodingProperties.GetAbrBitrateMin();
  1366. bitrate -= my_EncodingProperties.GetAbrBitrateStep())
  1367. {
  1368. if (bitrate >= mpeg2_bitrate[SIZE_BITRATE_MPEG2-1] && (!my_EncodingProperties.GetSmartOutputMode() || IsSmartOutput(mpeg2_freq[freq], bitrate, channel+1)))
  1369. {
  1370. bitrate_item * bitrate_table_tmp = new bitrate_item;
  1371. if (bitrate_table_tmp == NULL)
  1372. return;
  1373. bitrate_table_tmp->frequency = mpeg2_freq[freq];
  1374. bitrate_table_tmp->bitrate = bitrate;
  1375. bitrate_table_tmp->channels = channel+1;
  1376. bitrate_table_tmp->mode = vbr_abr;
  1377. bitrate_table.push_back(*bitrate_table_tmp);
  1378. }
  1379. }
  1380. }
  1381. }
  1382. }
  1383.  
  1384. // sorting by frequency/bitrate/channel
  1385. std::sort(bitrate_table.begin(), bitrate_table.end());
  1386.  
  1387. /* {
  1388. // display test
  1389. int i=0;
  1390. for (i=0; i<bitrate_table.size();i++)
  1391. {
  1392. my_debug.OutPut("bitrate_table[%d].frequency = %d",i,bitrate_table[i].frequency);
  1393. my_debug.OutPut("bitrate_table[%d].bitrate = %d",i,bitrate_table[i].bitrate);
  1394. my_debug.OutPut("bitrate_table[%d].channel = %d",i,bitrate_table[i].channels);
  1395. my_debug.OutPut("bitrate_table[%d].ABR = %s\n",i,(bitrate_table[i].mode == vbr_abr)?"ABR":"CBR");
  1396. }
  1397. }*/
  1398.  
  1399. my_debug.OutPut("leaving BuildBitrateTable");
  1400. }