Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

827 lines
20 KiB

  1. //--------------------------------------------------------------------------;
  2. //
  3. // File: dslevel.cpp
  4. //
  5. // Copyright (c) 1997 Microsoft Corporation. All rights reserved
  6. //
  7. //--------------------------------------------------------------------------;
  8. #include "mmcpl.h"
  9. #include <windowsx.h>
  10. #ifdef DEBUG
  11. #undef DEBUG
  12. #include <mmsystem.h>
  13. #define DEBUG
  14. #else
  15. #include <mmsystem.h>
  16. #endif
  17. #include <commctrl.h>
  18. #include <prsht.h>
  19. #include <regstr.h>
  20. #include "utils.h"
  21. #include "medhelp.h"
  22. #include "dslevel.h"
  23. #include "perfpage.h"
  24. #include "speakers.h"
  25. #include <initguid.h>
  26. #include <dsound.h>
  27. #include <dsprv.h>
  28. #define REG_KEY_SPEAKERTYPE TEXT("Speaker Type")
  29. typedef HRESULT (STDAPICALLTYPE *LPFNDLLGETCLASSOBJECT)(REFCLSID, REFIID, LPVOID *);
  30. typedef HRESULT (STDAPICALLTYPE *LPFNDIRECTSOUNDCREATE)(LPGUID, LPDIRECTSOUND*, IUnknown FAR *);
  31. typedef HRESULT (STDAPICALLTYPE *LPFNDIRECTSOUNDCAPTURECREATE)(LPGUID, LPDIRECTSOUNDCAPTURE*, IUnknown FAR *);
  32. HRESULT
  33. DirectSoundPrivateCreate
  34. (
  35. OUT LPKSPROPERTYSET * ppKsPropertySet
  36. )
  37. {
  38. HMODULE hLibDsound = NULL;
  39. LPFNDLLGETCLASSOBJECT pfnDllGetClassObject = NULL;
  40. LPCLASSFACTORY pClassFactory = NULL;
  41. LPKSPROPERTYSET pKsPropertySet = NULL;
  42. HRESULT hr = DS_OK;
  43. // Load dsound.dll
  44. hLibDsound = LoadLibrary(TEXT("dsound.dll"));
  45. if(!hLibDsound)
  46. {
  47. hr = DSERR_GENERIC;
  48. }
  49. // Find DllGetClassObject
  50. if(SUCCEEDED(hr))
  51. {
  52. pfnDllGetClassObject =
  53. (LPFNDLLGETCLASSOBJECT)GetProcAddress
  54. (
  55. hLibDsound,
  56. "DllGetClassObject"
  57. );
  58. if(!pfnDllGetClassObject)
  59. {
  60. hr = DSERR_GENERIC;
  61. }
  62. }
  63. // Create a class factory object
  64. if(SUCCEEDED(hr))
  65. {
  66. hr =
  67. pfnDllGetClassObject
  68. (
  69. CLSID_DirectSoundPrivate,
  70. IID_IClassFactory,
  71. (LPVOID *)&pClassFactory
  72. );
  73. }
  74. // Create the DirectSoundPrivate object and query for an IKsPropertySet
  75. // interface
  76. if(SUCCEEDED(hr))
  77. {
  78. hr =
  79. pClassFactory->CreateInstance
  80. (
  81. NULL,
  82. IID_IKsPropertySet,
  83. (LPVOID *)&pKsPropertySet
  84. );
  85. }
  86. // Release the class factory
  87. if(pClassFactory)
  88. {
  89. pClassFactory->Release();
  90. }
  91. // Handle final success or failure
  92. if(SUCCEEDED(hr))
  93. {
  94. *ppKsPropertySet = pKsPropertySet;
  95. }
  96. else if(pKsPropertySet)
  97. {
  98. pKsPropertySet->Release();
  99. }
  100. FreeLibrary(hLibDsound);
  101. return hr;
  102. }
  103. HRESULT
  104. DSGetGuidFromName
  105. (
  106. IN LPTSTR szName,
  107. IN BOOL fRecord,
  108. OUT LPGUID pGuid
  109. )
  110. {
  111. LPKSPROPERTYSET pKsPropertySet = NULL;
  112. HRESULT hr;
  113. DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING_DATA
  114. WaveDeviceMap;
  115. // Create the DirectSoundPrivate object
  116. hr =
  117. DirectSoundPrivateCreate
  118. (
  119. &pKsPropertySet
  120. );
  121. // Attempt to map the waveIn/waveOut device string to a DirectSound device
  122. // GUID.
  123. if(SUCCEEDED(hr))
  124. {
  125. WaveDeviceMap.DeviceName = szName;
  126. WaveDeviceMap.DataFlow = fRecord ? DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE : DIRECTSOUNDDEVICE_DATAFLOW_RENDER;
  127. hr =
  128. pKsPropertySet->Get
  129. (
  130. DSPROPSETID_DirectSoundDevice,
  131. DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING,
  132. NULL,
  133. 0,
  134. &WaveDeviceMap,
  135. sizeof(WaveDeviceMap),
  136. NULL
  137. );
  138. }
  139. // Clean up
  140. if(pKsPropertySet)
  141. {
  142. pKsPropertySet->Release();
  143. }
  144. if(SUCCEEDED(hr))
  145. {
  146. *pGuid = WaveDeviceMap.DeviceId;
  147. }
  148. return hr;
  149. }
  150. HRESULT
  151. DSSetupFunctions
  152. (
  153. LPFNDIRECTSOUNDCREATE* pfnDSCreate,
  154. LPFNDIRECTSOUNDCAPTURECREATE* pfnDSCaptureCreate
  155. )
  156. {
  157. HMODULE hLibDsound = NULL;
  158. HRESULT hr = DS_OK;
  159. // Load dsound.dll
  160. hLibDsound = LoadLibrary(TEXT("dsound.dll"));
  161. if(!hLibDsound)
  162. {
  163. hr = DSERR_GENERIC;
  164. }
  165. // Find DirectSoundCreate
  166. if(SUCCEEDED(hr))
  167. {
  168. *pfnDSCreate =
  169. (LPFNDIRECTSOUNDCREATE)GetProcAddress
  170. (
  171. hLibDsound,
  172. "DirectSoundCreate"
  173. );
  174. if(!(*pfnDSCreate))
  175. {
  176. hr = DSERR_GENERIC;
  177. }
  178. } //end DirectSoundCreate
  179. // Find DirectSoundCaptureCreate
  180. if(SUCCEEDED(hr))
  181. {
  182. *pfnDSCaptureCreate =
  183. (LPFNDIRECTSOUNDCAPTURECREATE)GetProcAddress
  184. (
  185. hLibDsound,
  186. "DirectSoundCaptureCreate"
  187. );
  188. if(!(*pfnDSCaptureCreate))
  189. {
  190. hr = DSERR_GENERIC;
  191. }
  192. } //end DirectSoundCaptureCreate
  193. FreeLibrary(hLibDsound);
  194. return (hr);
  195. }
  196. void
  197. DSCleanup
  198. (
  199. IN LPKSPROPERTYSET pKsPropertySet,
  200. IN LPDIRECTSOUND pDirectSound,
  201. IN LPDIRECTSOUNDCAPTURE pDirectSoundCapture
  202. )
  203. {
  204. if(pKsPropertySet)
  205. {
  206. pKsPropertySet->Release();
  207. }
  208. if(pDirectSound)
  209. {
  210. pDirectSound->Release();
  211. }
  212. if(pDirectSoundCapture)
  213. {
  214. pDirectSoundCapture->Release();
  215. }
  216. }
  217. HRESULT
  218. DSInitialize
  219. (
  220. IN GUID guid,
  221. IN BOOL fRecord,
  222. OUT LPKSPROPERTYSET* ppKsPropertySet,
  223. OUT LPLPDIRECTSOUND ppDirectSound,
  224. OUT LPLPDIRECTSOUNDCAPTURE ppDirectSoundCapture
  225. )
  226. {
  227. HRESULT hr;
  228. LPFNDIRECTSOUNDCREATE pfnDirectSoundCreate = NULL;
  229. LPFNDIRECTSOUNDCAPTURECREATE pfnDirectSoundCaptureCreate = NULL;
  230. // Initialize variables to return
  231. *ppKsPropertySet = NULL;
  232. *ppDirectSound = NULL;
  233. *ppDirectSoundCapture = NULL;
  234. // Find the necessary DirectSound functions
  235. hr = DSSetupFunctions(&pfnDirectSoundCreate, &pfnDirectSoundCaptureCreate);
  236. if (FAILED(hr))
  237. {
  238. return (hr);
  239. }
  240. // Create the DirectSound object
  241. if(fRecord)
  242. {
  243. hr =
  244. pfnDirectSoundCaptureCreate
  245. (
  246. &guid,
  247. ppDirectSoundCapture,
  248. NULL
  249. );
  250. }
  251. else
  252. {
  253. hr =
  254. pfnDirectSoundCreate
  255. (
  256. &guid,
  257. ppDirectSound,
  258. NULL
  259. );
  260. }
  261. // Create the DirectSoundPrivate object
  262. if(SUCCEEDED(hr))
  263. {
  264. hr =
  265. DirectSoundPrivateCreate
  266. (
  267. ppKsPropertySet
  268. );
  269. }
  270. // Clean up
  271. if(FAILED(hr))
  272. {
  273. DSCleanup
  274. (
  275. *ppKsPropertySet,
  276. *ppDirectSound,
  277. *ppDirectSoundCapture
  278. );
  279. *ppKsPropertySet = NULL;
  280. *ppDirectSound = NULL;
  281. *ppDirectSoundCapture = NULL;
  282. }
  283. return hr;
  284. }
  285. HRESULT
  286. DSGetAcceleration
  287. (
  288. IN GUID guid,
  289. IN BOOL fRecord,
  290. OUT LPDWORD pdwHWLevel
  291. )
  292. {
  293. LPKSPROPERTYSET pKsPropertySet = NULL;
  294. LPDIRECTSOUND pDirectSound = NULL;
  295. LPDIRECTSOUNDCAPTURE pDirectSoundCapture = NULL;
  296. HRESULT hr;
  297. DSPROPERTY_DIRECTSOUNDBASICACCELERATION_ACCELERATION_DATA
  298. BasicAcceleration;
  299. // Find the necessary DirectSound functions
  300. hr = DSInitialize(guid, fRecord, &pKsPropertySet, &pDirectSound, &pDirectSoundCapture );
  301. // Get properties for this device
  302. if(SUCCEEDED(hr))
  303. {
  304. BasicAcceleration.DeviceId = guid;
  305. // Get the default acceleration level
  306. hr =
  307. pKsPropertySet->Get
  308. (
  309. DSPROPSETID_DirectSoundBasicAcceleration,
  310. DSPROPERTY_DIRECTSOUNDBASICACCELERATION_DEFAULT,
  311. NULL,
  312. 0,
  313. &BasicAcceleration,
  314. sizeof(BasicAcceleration),
  315. NULL
  316. );
  317. if (SUCCEEDED(hr))
  318. {
  319. gAudData.dwDefaultHWLevel = BasicAcceleration.Level;
  320. }
  321. // Get the basic HW acceleration level. This property will return
  322. // S_FALSE if no error occurred, but the registry value did not exist.
  323. hr =
  324. pKsPropertySet->Get
  325. (
  326. DSPROPSETID_DirectSoundBasicAcceleration,
  327. DSPROPERTY_DIRECTSOUNDBASICACCELERATION_ACCELERATION,
  328. NULL,
  329. 0,
  330. &BasicAcceleration,
  331. sizeof(BasicAcceleration),
  332. NULL
  333. );
  334. if(SUCCEEDED(hr))
  335. {
  336. *pdwHWLevel = BasicAcceleration.Level;
  337. }
  338. else
  339. {
  340. *pdwHWLevel = gAudData.dwDefaultHWLevel;
  341. }
  342. }
  343. // Clean up
  344. DSCleanup
  345. (
  346. pKsPropertySet,
  347. pDirectSound,
  348. pDirectSoundCapture
  349. );
  350. return hr;
  351. }
  352. HRESULT
  353. DSGetSrcQuality
  354. (
  355. IN GUID guid,
  356. IN BOOL fRecord,
  357. OUT LPDWORD pdwSRCLevel
  358. )
  359. {
  360. LPKSPROPERTYSET pKsPropertySet = NULL;
  361. LPDIRECTSOUND pDirectSound = NULL;
  362. LPDIRECTSOUNDCAPTURE pDirectSoundCapture = NULL;
  363. HRESULT hr;
  364. DSPROPERTY_DIRECTSOUNDMIXER_SRCQUALITY_DATA
  365. SrcQuality;
  366. // Find the necessary DirectSound functions
  367. hr = DSInitialize(guid, fRecord, &pKsPropertySet, &pDirectSound, &pDirectSoundCapture );
  368. // Get properties for this device
  369. if(SUCCEEDED(hr))
  370. {
  371. // Get the mixer SRC quality. This property will return S_FALSE
  372. // if no error occurred, but the registry value did not exist.
  373. SrcQuality.DeviceId = guid;
  374. hr =
  375. pKsPropertySet->Get
  376. (
  377. DSPROPSETID_DirectSoundMixer,
  378. DSPROPERTY_DIRECTSOUNDMIXER_SRCQUALITY,
  379. NULL,
  380. 0,
  381. &SrcQuality,
  382. sizeof(SrcQuality),
  383. NULL
  384. );
  385. if(SUCCEEDED(hr))
  386. {
  387. // The CPL only uses the 3 highest of 4 possible SRC values
  388. *pdwSRCLevel = SrcQuality.Quality;
  389. if(*pdwSRCLevel > 0)
  390. {
  391. (*pdwSRCLevel)--;
  392. }
  393. }
  394. else
  395. {
  396. *pdwSRCLevel = DEFAULT_SRC_LEVEL;
  397. }
  398. }
  399. // Clean up
  400. DSCleanup
  401. (
  402. pKsPropertySet,
  403. pDirectSound,
  404. pDirectSoundCapture
  405. );
  406. return hr;
  407. }
  408. HRESULT
  409. DSGetSpeakerConfigType
  410. (
  411. IN GUID guid,
  412. IN BOOL fRecord,
  413. OUT LPDWORD pdwSpeakerConfig,
  414. OUT LPDWORD pdwSpeakerType
  415. )
  416. {
  417. LPKSPROPERTYSET pKsPropertySet = NULL;
  418. LPDIRECTSOUND pDirectSound = NULL;
  419. LPDIRECTSOUNDCAPTURE pDirectSoundCapture = NULL;
  420. HRESULT hr = DS_OK;
  421. HRESULT hrSpeakerType;
  422. DSPROPERTY_DIRECTSOUNDPERSISTENTDATA_PERSISTDATA_DATA
  423. SpeakerType;
  424. // Can't get the speaker type if we're recording
  425. if(fRecord)
  426. {
  427. hr = E_INVALIDARG;
  428. }
  429. // Find the necessary DirectSound functions
  430. if(SUCCEEDED(hr))
  431. {
  432. hr = DSInitialize(guid, fRecord, &pKsPropertySet, &pDirectSound, &pDirectSoundCapture );
  433. }
  434. // Get properties for this device
  435. if(SUCCEEDED(hr))
  436. {
  437. // Get the speaker config
  438. hr =
  439. pDirectSound->GetSpeakerConfig
  440. (
  441. pdwSpeakerConfig
  442. );
  443. if(FAILED(hr))
  444. {
  445. *pdwSpeakerConfig = DSSPEAKER_STEREO;
  446. }
  447. // Get the speaker type. This property will return failure
  448. // if the registry value doesn't exist.
  449. SpeakerType.DeviceId = guid;
  450. SpeakerType.SubKeyName = REG_KEY_SPEAKERTYPE;
  451. SpeakerType.ValueName = REG_KEY_SPEAKERTYPE;
  452. SpeakerType.RegistryDataType = REG_DWORD;
  453. SpeakerType.Data = pdwSpeakerType;
  454. SpeakerType.DataSize = sizeof(pdwSpeakerType);
  455. hrSpeakerType =
  456. pKsPropertySet->Get
  457. (
  458. DSPROPSETID_DirectSoundPersistentData,
  459. DSPROPERTY_DIRECTSOUNDPERSISTENTDATA_PERSISTDATA,
  460. NULL,
  461. 0,
  462. &SpeakerType,
  463. sizeof(SpeakerType),
  464. NULL
  465. );
  466. if(FAILED(hrSpeakerType))
  467. {
  468. *pdwSpeakerType = SPEAKERS_DEFAULT_TYPE;
  469. }
  470. }
  471. // Clean up
  472. DSCleanup
  473. (
  474. pKsPropertySet,
  475. pDirectSound,
  476. pDirectSoundCapture
  477. );
  478. return hr;
  479. }
  480. HRESULT
  481. DSGetCplValues
  482. (
  483. IN GUID guid,
  484. IN BOOL fRecord,
  485. OUT LPCPLDATA pData
  486. )
  487. {
  488. HRESULT hr;
  489. // Get the basic HW acceleration level.
  490. pData->dwHWLevel = gAudData.dwDefaultHWLevel;
  491. hr = DSGetAcceleration
  492. (
  493. guid,
  494. fRecord,
  495. &pData->dwHWLevel
  496. );
  497. // Get the mixer SRC quality.
  498. pData->dwSRCLevel = DEFAULT_SRC_LEVEL;
  499. hr = DSGetSrcQuality
  500. (
  501. guid,
  502. fRecord,
  503. &pData->dwSRCLevel
  504. );
  505. // Get playback-specific settings
  506. if(!fRecord)
  507. {
  508. // Get the speaker config
  509. pData->dwSpeakerConfig = DSSPEAKER_STEREO;
  510. pData->dwSpeakerType = SPEAKERS_DEFAULT_TYPE;
  511. hr = DSGetSpeakerConfigType
  512. (
  513. guid,
  514. fRecord,
  515. &pData->dwSpeakerConfig,
  516. &pData->dwSpeakerType
  517. );
  518. }
  519. return DS_OK;
  520. }
  521. HRESULT
  522. DSSetAcceleration
  523. (
  524. IN GUID guid,
  525. IN BOOL fRecord,
  526. IN DWORD dwHWLevel
  527. )
  528. {
  529. LPKSPROPERTYSET pKsPropertySet = NULL;
  530. LPDIRECTSOUND pDirectSound = NULL;
  531. LPDIRECTSOUNDCAPTURE pDirectSoundCapture = NULL;
  532. HRESULT hr;
  533. DSPROPERTY_DIRECTSOUNDBASICACCELERATION_ACCELERATION_DATA
  534. BasicAcceleration;
  535. // Find the necessary DirectSound functions
  536. hr = DSInitialize(guid, fRecord, &pKsPropertySet, &pDirectSound, &pDirectSoundCapture );
  537. // Get properties for this device
  538. if(SUCCEEDED(hr))
  539. {
  540. BasicAcceleration.DeviceId = guid;
  541. BasicAcceleration.Level = (DIRECTSOUNDBASICACCELERATION_LEVEL)dwHWLevel;
  542. // Set the basic HW acceleration level
  543. hr =
  544. pKsPropertySet->Set
  545. (
  546. DSPROPSETID_DirectSoundBasicAcceleration,
  547. DSPROPERTY_DIRECTSOUNDBASICACCELERATION_ACCELERATION,
  548. NULL,
  549. 0,
  550. &BasicAcceleration,
  551. sizeof(BasicAcceleration)
  552. );
  553. }
  554. // Clean up
  555. DSCleanup
  556. (
  557. pKsPropertySet,
  558. pDirectSound,
  559. pDirectSoundCapture
  560. );
  561. return hr;
  562. }
  563. HRESULT
  564. DSSetSrcQuality
  565. (
  566. IN GUID guid,
  567. IN BOOL fRecord,
  568. IN DWORD dwSRCLevel
  569. )
  570. {
  571. LPKSPROPERTYSET pKsPropertySet = NULL;
  572. LPDIRECTSOUND pDirectSound = NULL;
  573. LPDIRECTSOUNDCAPTURE pDirectSoundCapture = NULL;
  574. HRESULT hr;
  575. DSPROPERTY_DIRECTSOUNDMIXER_SRCQUALITY_DATA
  576. SrcQuality;
  577. // Find the necessary DirectSound functions
  578. hr = DSInitialize(guid, fRecord, &pKsPropertySet, &pDirectSound, &pDirectSoundCapture );
  579. // Get properties for this device
  580. if(SUCCEEDED(hr))
  581. {
  582. SrcQuality.DeviceId = guid;
  583. // The CPL only uses the 3 highest of 4 possible SRC values
  584. SrcQuality.Quality = (DIRECTSOUNDMIXER_SRCQUALITY)(dwSRCLevel + 1);
  585. // Set the mixer SRC quality
  586. hr =
  587. pKsPropertySet->Set
  588. (
  589. DSPROPSETID_DirectSoundMixer,
  590. DSPROPERTY_DIRECTSOUNDMIXER_SRCQUALITY,
  591. NULL,
  592. 0,
  593. &SrcQuality,
  594. sizeof(SrcQuality)
  595. );
  596. }
  597. // Clean up
  598. DSCleanup
  599. (
  600. pKsPropertySet,
  601. pDirectSound,
  602. pDirectSoundCapture
  603. );
  604. return hr;
  605. }
  606. HRESULT
  607. DSSetSpeakerConfigType
  608. (
  609. IN GUID guid,
  610. IN BOOL fRecord,
  611. IN DWORD dwSpeakerConfig,
  612. IN DWORD dwSpeakerType
  613. )
  614. {
  615. LPKSPROPERTYSET pKsPropertySet = NULL;
  616. LPDIRECTSOUND pDirectSound = NULL;
  617. LPDIRECTSOUNDCAPTURE pDirectSoundCapture = NULL;
  618. HRESULT hr = DS_OK;
  619. DSPROPERTY_DIRECTSOUNDPERSISTENTDATA_PERSISTDATA_DATA
  620. SpeakerType;
  621. // Can't set the speaker type if we're recording
  622. if(fRecord)
  623. {
  624. hr = E_INVALIDARG;
  625. }
  626. // Find the necessary DirectSound functions
  627. if(SUCCEEDED(hr))
  628. {
  629. hr = DSInitialize(guid, fRecord, &pKsPropertySet, &pDirectSound, &pDirectSoundCapture );
  630. }
  631. // Set the speaker config
  632. if(SUCCEEDED(hr))
  633. {
  634. hr =
  635. pDirectSound->SetSpeakerConfig
  636. (
  637. dwSpeakerConfig
  638. );
  639. }
  640. // Set the speaker type
  641. if(SUCCEEDED(hr))
  642. {
  643. SpeakerType.DeviceId = guid;
  644. SpeakerType.SubKeyName = REG_KEY_SPEAKERTYPE;
  645. SpeakerType.ValueName = REG_KEY_SPEAKERTYPE;
  646. SpeakerType.RegistryDataType = REG_DWORD;
  647. SpeakerType.Data = &dwSpeakerType;
  648. SpeakerType.DataSize = sizeof(dwSpeakerType);
  649. hr =
  650. pKsPropertySet->Set
  651. (
  652. DSPROPSETID_DirectSoundPersistentData,
  653. DSPROPERTY_DIRECTSOUNDPERSISTENTDATA_PERSISTDATA,
  654. NULL,
  655. 0,
  656. &SpeakerType,
  657. sizeof(SpeakerType)
  658. );
  659. }
  660. // Clean up
  661. DSCleanup
  662. (
  663. pKsPropertySet,
  664. pDirectSound,
  665. pDirectSoundCapture
  666. );
  667. return hr;
  668. }
  669. HRESULT
  670. DSSetCplValues
  671. (
  672. IN GUID guid,
  673. IN BOOL fRecord,
  674. IN const LPCPLDATA pData
  675. )
  676. {
  677. HRESULT hr;
  678. // Set the basic HW acceleration level
  679. hr =
  680. DSSetAcceleration
  681. (
  682. guid,
  683. fRecord,
  684. pData->dwHWLevel
  685. );
  686. // Set the mixer SRC quality
  687. if(SUCCEEDED(hr))
  688. {
  689. hr =
  690. DSSetSrcQuality
  691. (
  692. guid,
  693. fRecord,
  694. pData->dwSRCLevel // +1 is done in DSSetSrcQuality
  695. );
  696. }
  697. // Set the speaker config
  698. if(SUCCEEDED(hr) && !fRecord)
  699. {
  700. DSSetSpeakerConfigType
  701. (
  702. guid,
  703. fRecord,
  704. pData->dwSpeakerConfig,
  705. pData->dwSpeakerType
  706. );
  707. }
  708. return hr;
  709. }