vbox的更動 67648 路徑 trunk/src/VBox/Devices/Audio/DevIchAc97.cpp
- 時間撮記:
- 2017-6-27 下午04:44:40 (7 年 以前)
- 檔案:
-
- 修改 1 筆資料
圖例:
- 未更動
- 新增
- 刪除
-
trunk/src/VBox/Devices/Audio/DevIchAc97.cpp
r67362 r67648 467 467 static void ichac97StreamLock(PAC97STREAM pStream); 468 468 static void ichac97StreamUnlock(PAC97STREAM pStream); 469 static uint32_t ichac97StreamGetUsed(PAC97STREAM pStream); 470 static uint32_t ichac97StreamGetFree(PAC97STREAM pStream); 471 static int ichac97StreamTransfer(PAC97STATE pThis, PAC97STREAM pStream, uint32_t cbToProcessMax); 469 472 470 473 static DECLCALLBACK(void) ichac97Reset(PPDMDEVINS pDevIns); … … 475 478 static DECLCALLBACK(void) ichac97Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser); 476 479 #endif 477 static int ichac97DoDMA(PAC97STATE pThis, PAC97STREAM pStream, void *pvBuf, uint32_t cbBuf, uint32_t cbToProcess, uint32_t *pcbProcessed);478 480 static void ichac97DoTransfers(PAC97STATE pThis); 479 481 … … 1187 1189 1188 1190 /** 1189 * Updates an AC'97 stream according to its usage (input / output). 1190 * 1191 * For an SDO (output) stream this means reading DMA data from the device to 1192 * the connected audio sink(s). 1193 * 1194 * For an SDI (input) stream this is reading audio data from the connected 1195 * audio sink(s) and writing it as DMA data to the device. 1196 * 1197 * @returns IPRT status code. 1191 * Updates an AC'97 stream by doing its required data transfers. 1192 * The host sink(s) set the overall pace. 1193 * 1194 * This routine is called by both, the synchronous and the asynchronous, implementations. 1195 * 1198 1196 * @param pThis AC'97 state. 1199 1197 * @param pStream AC'97 stream to update. 1200 */ 1201 static int ichac97StreamUpdate(PAC97STATE pThis, PAC97STREAM pStream) 1202 { 1203 AssertPtrReturn(pThis, VERR_INVALID_POINTER); 1204 AssertPtrReturn(pStream, VERR_INVALID_POINTER); 1205 1206 ichac97StreamLock(pStream); 1207 1208 PAUDMIXSINK pMixSink = ichac97IndexToSink(pThis, pStream->u8SD); 1209 AssertPtr(pMixSink); 1210 1211 if (!AudioMixerSinkIsActive(pMixSink)) 1212 { 1213 ichac97StreamUnlock(pStream); 1214 return VINF_SUCCESS; 1215 } 1216 1217 PRTCIRCBUF pCircBuf = pStream->State.pCircBuf; 1218 AssertPtr(pCircBuf); 1219 1220 bool fDone = false; 1221 uint8_t cTransfers = 0; 1222 1223 Log2Func(("[SD%RU8] Started\n", pStream->u8SD)); 1224 1225 while (!fDone) 1226 { 1227 int rc2; 1228 uint32_t cbDMA = 0; 1229 1230 if (pStream->u8SD == AC97SOUNDSOURCE_PO_INDEX) /* Output. */ 1231 { 1232 STAM_PROFILE_START(&pThis->StatOut, a); 1233 1234 /* 1235 * Read from DMA. 1236 */ 1237 uint8_t abFIFO[AC97_FIFO_MAX + 1]; 1238 size_t offFIFO = 0; 1239 1240 /* Do one DMA transfer with FIFOS size at a time. */ 1241 rc2 = ichac97DoDMA(pThis, pStream, abFIFO, sizeof(abFIFO), AC97_FIFO_MAX /** @todo FIFOS? */, &cbDMA); 1198 * @param fInTimer Whether to this function was called from the timer 1199 * context or an asynchronous I/O stream thread (if supported). 1200 */ 1201 static void ichac97StreamUpdate(PAC97STATE pThis, PAC97STREAM pStream, bool fInTimer) 1202 { 1203 PAUDMIXSINK pSink = ichac97IndexToSink(pThis, pStream->u8SD); 1204 AssertPtr(pSink); 1205 1206 if (!AudioMixerSinkIsActive(pSink)) /* No sink available? Bail out. */ 1207 return; 1208 1209 int rc2; 1210 1211 if (pStream->u8SD == AC97SOUNDSOURCE_PO_INDEX) /* Output (SDO). */ 1212 { 1213 /* Is the AC'97 stream ready to be written (guest output data) to? If so, by how much? */ 1214 const uint32_t cbFree = ichac97StreamGetFree(pStream); 1215 1216 if ( fInTimer 1217 && cbFree) 1218 { 1219 Log3Func(("[SD%RU8] cbFree=%RU32\n", pStream->u8SD, cbFree)); 1220 1221 /* Do the DMA transfer. */ 1222 rc2 = ichac97StreamTransfer(pThis, pStream, cbFree); 1242 1223 AssertRC(rc2); 1243 1244 uint32_t cbDMALeft = cbDMA; 1245 1246 while ( cbDMALeft 1247 && RTCircBufFree(pCircBuf)) 1248 { 1249 Log3Func(("[SD%RU8] cbLeft=%RU32\n", pStream->u8SD, cbDMALeft)); 1250 1251 void *pvDst; 1252 size_t cbDst; 1253 1254 RTCircBufAcquireWriteBlock(pCircBuf, cbDMALeft, &pvDst, &cbDst); 1255 1256 if (cbDst) 1257 { 1258 memcpy(pvDst, abFIFO + offFIFO, cbDst); 1259 1260 offFIFO += cbDst; 1261 Assert(offFIFO <= sizeof(abFIFO)); 1262 } 1263 1264 RTCircBufReleaseWriteBlock(pCircBuf, cbDst); 1265 1266 Assert(cbDst <= cbDMALeft); 1267 cbDMALeft -= (uint32_t)cbDst; 1268 } 1269 1270 #ifdef DEBUG_andy 1271 AssertMsg(cbDMALeft == 0, ("%RU32 bytes of DMA data left, CircBuf=%zu/%zu\n", 1272 cbDMALeft, RTCircBufUsed(pCircBuf), RTCircBufSize(pCircBuf))); 1224 } 1225 1226 /* How much (guest output) data is available at the moment for the HDA stream? */ 1227 uint32_t cbUsed = ichac97StreamGetUsed(pStream); 1228 1229 #ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO 1230 if ( fInTimer 1231 && cbUsed) 1232 { 1233 rc2 = ichac97StreamAsyncIONotify(pThis, pStream); 1234 AssertRC(rc2); 1235 } 1236 else 1237 { 1273 1238 #endif 1274 /* 1275 * Process backends. 1276 */ 1277 1278 /* Do we have data left to write to the backends? */ 1279 uint32_t cbUsed = (uint32_t)RTCircBufUsed(pCircBuf); 1239 const uint32_t cbSinkWritable = AudioMixerSinkGetWritable(pSink); 1240 1241 /* Do not write more than the sink can hold at the moment. 1242 * The host sets the overall pace. */ 1243 if (cbUsed > cbSinkWritable) 1244 cbUsed = cbSinkWritable; 1245 1280 1246 if (cbUsed) 1281 1247 { 1282 Log3Func(("[SD%RU8] cbUsed=%RU32\n", pStream->u8SD, cbUsed)); 1283 1284 #ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO 1285 /* Let the asynchronous thread know that there is some new data to process. */ 1286 ichac97StreamAsyncIONotify(pThis, pStream); 1287 #else 1288 /* Read audio data from the AC'97 stream and write to the backends. */ 1289 rc2 = ichac97StreamRead(pThis, pStream, pMixSink, cbUsed, NULL /* pcbRead */); 1290 AssertRC(rc2); 1291 #endif 1292 } 1293 1294 /* All DMA transfers done for now? */ 1295 if ( !cbDMA 1296 #ifndef VBOX_WITH_AUDIO_AC97_ASYNC_IO 1297 /* All data read *and* processed for now? */ 1298 && RTCircBufUsed(pCircBuf) == 0 1299 #endif 1300 ) 1301 { 1302 fDone = true; 1303 } 1304 1305 #ifndef VBOX_WITH_AUDIO_AC97_ASYNC_IO 1306 rc2 = AudioMixerSinkUpdate(pMixSink); 1307 AssertRC(rc2); 1308 #endif 1309 STAM_PROFILE_STOP(&pThis->StatOut, a); 1310 } 1311 else if ( pStream->u8SD == AC97SOUNDSOURCE_PI_INDEX /* Input. */ 1312 || pStream->u8SD == AC97SOUNDSOURCE_MC_INDEX) /* Input. */ 1313 { 1314 STAM_PROFILE_START(&pThis->StatIn, a); 1315 1316 /* 1317 * Process backends. 1318 */ 1319 1320 #ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO 1321 /* Let the asynchronous thread know that there is some new data to process. */ 1322 ichac97StreamAsyncIONotify(pThis, pStream); 1323 #else 1324 rc2 = AudioMixerSinkUpdate(pMixSink); 1325 AssertRC(rc2); 1326 1327 /* Write read data from the backend to the AC'97 stream. */ 1328 rc2 = ichac97StreamWrite(pThis, pStream, pMixSink, 256 /** @todo Fix this! */, NULL /* pcbWritten */); 1329 AssertRC(rc2); 1330 #endif 1331 /* 1332 * Write to DMA. 1333 */ 1334 void *pvSrc; 1335 size_t cbSrc; 1336 1337 RTCircBufAcquireReadBlock(pCircBuf, 256 /** @todo Fix this! */, &pvSrc, &cbSrc); 1338 1339 if (cbSrc) 1340 { 1341 /* Do one DMA transfer with FIFOS size at a time. */ 1342 rc2 = ichac97DoDMA(pThis, pStream, pvSrc, (uint32_t)cbSrc, (uint32_t)cbSrc /* cbToProcess */, &cbDMA); 1248 /* Read (guest output) data and write it to the stream's sink. */ 1249 rc2 = ichac97StreamRead(pThis, pStream, pSink, cbUsed, NULL /* pcbRead */); 1343 1250 AssertRC(rc2); 1344 1251 } 1345 1252 1346 RTCircBufReleaseReadBlock(pCircBuf, cbDMA); 1347 1348 /* All DMA transfers done for now? */ 1349 if (!cbDMA) 1350 fDone = true; 1351 1352 STAM_PROFILE_STOP(&pThis->StatIn, a); 1253 /* When running synchronously, update the associated sink here. 1254 * Otherwise this will be done in the device timer. */ 1255 rc2 = AudioMixerSinkUpdate(pSink); 1256 AssertRC(rc2); 1257 1258 #ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO 1259 } 1260 #endif 1261 } 1262 else /* Input (SDI). */ 1263 { 1264 #ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO 1265 if (fInTimer) 1266 { 1267 rc2 = ichac97StreamAsyncIONotify(pThis, pStream); 1268 AssertRC(rc2); 1353 1269 } 1354 1270 else 1355 AssertFailed(); 1356 1357 if (++cTransfers > 32) /* Failsafe counter. */ 1358 fDone = true; 1359 1360 } /* while !fDone */ 1361 1362 Log2Func(("[SD%RU8] End\n", pStream->u8SD)); 1363 1364 ichac97StreamUnlock(pStream); 1365 1366 return VINF_SUCCESS; 1271 { 1272 #endif 1273 rc2 = AudioMixerSinkUpdate(pSink); 1274 AssertRC(rc2); 1275 1276 /* Is the sink ready to be read (host input data) from? If so, by how much? */ 1277 const uint32_t cbReadable = AudioMixerSinkGetReadable(pSink); 1278 1279 /* How much (guest input) data is free at the moment? */ 1280 uint32_t cbFree = ichac97StreamGetFree(pStream); 1281 1282 Log3Func(("[SD%RU8] cbReadable=%RU32, cbFree=%RU32\n", pStream->u8SD, cbReadable, cbFree)); 1283 1284 /* Do not read more than the sink can provide at the moment. 1285 * The host sets the overall pace. */ 1286 if (cbFree > cbReadable) 1287 cbFree = cbReadable; 1288 1289 if (cbFree) 1290 { 1291 /* Write (guest input) data to the stream which was read from stream's sink before. */ 1292 rc2 = ichac97StreamWrite(pThis, pStream, pSink, cbFree, NULL /* pcbWritten */); 1293 AssertRC(rc2); 1294 } 1295 #ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO 1296 } 1297 #endif 1298 1299 #ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO 1300 if (fInTimer) 1301 { 1302 #endif 1303 const uint32_t cbToTransfer = ichac97StreamGetUsed(pStream); 1304 if (cbToTransfer) 1305 { 1306 /* When running synchronously, do the DMA data transfers here. 1307 * Otherwise this will be done in the stream's async I/O thread. */ 1308 rc2 = ichac97StreamTransfer(pThis, pStream, cbToTransfer); 1309 AssertRC(rc2); 1310 } 1311 #ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO 1312 } 1313 #endif 1314 } 1367 1315 } 1368 1316 … … 1685 1633 } 1686 1634 1687 1688 1635 /** 1689 1636 * Unlocks a formerly locked AC'97 stream. … … 1697 1644 int rc2 = RTCritSectLeave(&pStream->State.CritSect); 1698 1645 AssertRC(rc2); 1646 } 1647 1648 /** 1649 * Retrieves the available size of (buffered) audio data (in bytes) of a given AC'97 stream. 1650 * 1651 * @returns Available data (in bytes). 1652 * @param pStream AC'97 stream to retrieve size for. 1653 */ 1654 static uint32_t ichac97StreamGetUsed(PAC97STREAM pStream) 1655 { 1656 AssertPtrReturn(pStream, 0); 1657 1658 if (!pStream->State.pCircBuf) 1659 return 0; 1660 1661 return (uint32_t)RTCircBufUsed(pStream->State.pCircBuf); 1662 } 1663 1664 /** 1665 * Retrieves the free size of audio data (in bytes) of a given AC'97 stream. 1666 * 1667 * @returns Free data (in bytes). 1668 * @param pStream AC'97 stream to retrieve size for. 1669 */ 1670 static uint32_t ichac97StreamGetFree(PAC97STREAM pStream) 1671 { 1672 AssertPtrReturn(pStream, 0); 1673 1674 if (!pStream->State.pCircBuf) 1675 return 0; 1676 1677 return (uint32_t)RTCircBufFree(pStream->State.pCircBuf); 1699 1678 } 1700 1679 … … 2137 2116 AssertPtrReturnVoid(pThis); 2138 2117 2139 ichac97StreamUpdate(pThis, &pThis->StreamLineIn); 2140 ichac97StreamUpdate(pThis, &pThis->StreamMicIn); 2141 ichac97StreamUpdate(pThis, &pThis->StreamOut); 2142 } 2143 2144 /** 2145 * Does a single DMA transfer for a specific AC'97 stream. 2146 * This either can be a read or write operation, depending on the AC'97 stream. 2118 ichac97StreamUpdate(pThis, &pThis->StreamLineIn, true /* fInTimer */); 2119 ichac97StreamUpdate(pThis, &pThis->StreamMicIn, true /* fInTimer */); 2120 ichac97StreamUpdate(pThis, &pThis->StreamOut, true /* fInTimer */); 2121 } 2122 2123 /** 2124 * Transfers data of an AC'97 stream according to its usage (input / output). 2125 * 2126 * For an SDO (output) stream this means reading DMA data from the device to 2127 * the HDA stream's internal FIFO buffer. 2128 * 2129 * For an SDI (input) stream this is reading audio data from the HDA stream's 2130 * internal FIFO buffer and writing it as DMA data to the device. 2147 2131 * 2148 2132 * @returns IPRT status code. 2149 2133 * @param pThis AC'97 state. 2150 * @param pStream AC'97 stream to do the DMA transfer for. 2151 * @param pvBuf Pointer to buffer data to write data to / read data from. 2152 * @param cbBuf Size of buffer (in bytes). 2153 * @param cbToProcess Size (in bytes) to transfer (read/write). 2154 * @param pcbProcessed Size (in bytes) transferred (read/written). Optional. 2155 */ 2156 static int ichac97DoDMA(PAC97STATE pThis, PAC97STREAM pStream, void *pvBuf, uint32_t cbBuf, 2157 uint32_t cbToProcess, uint32_t *pcbProcessed) 2158 { 2159 AssertPtrReturn(pThis, VERR_INVALID_POINTER); 2160 AssertPtrReturn(pStream, VERR_INVALID_POINTER); 2161 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); 2162 AssertReturn(cbBuf >= cbToProcess, VERR_INVALID_PARAMETER); 2163 /* pcbProcessed is optional. */ 2134 * @param pStream AC'97 stream to update. 2135 * @param cbToProcessMax Maximum of data (in bytes) to process. 2136 */ 2137 static int ichac97StreamTransfer(PAC97STATE pThis, PAC97STREAM pStream, uint32_t cbToProcessMax) 2138 { 2139 AssertPtrReturn(pThis, VERR_INVALID_POINTER); 2140 AssertPtrReturn(pStream, VERR_INVALID_POINTER); 2141 AssertReturn(cbToProcessMax, VERR_INVALID_PARAMETER); 2164 2142 2165 2143 PAC97BMREGS pRegs = &pStream->Regs; … … 2180 2158 } 2181 2159 2182 if (pcbProcessed)2183 *pcbProcessed = 0;2184 2185 2160 return VINF_SUCCESS; 2186 2161 } … … 2190 2165 { 2191 2166 Log3Func(("[SD%RU8] BCIS set\n", pStream->u8SD)); 2192 2193 if (pcbProcessed)2194 *pcbProcessed = 0;2195 2196 2167 return VINF_SUCCESS; 2197 2168 } 2198 2169 2199 uint32_t cbLeft = RT_MIN((uint32_t)(pRegs->picb << 1), RT_MIN(cbToProcess, cbBuf)); 2200 uint32_t cbTotal = 0; 2201 uint32_t cbChunk; 2170 uint32_t cbLeft = RT_MIN((uint32_t)(pRegs->picb << 1), cbToProcessMax); /** @todo r=andy Assumes 16bit samples. */ 2171 uint32_t cbProcessedTotal = 0; 2172 2173 PRTCIRCBUF pCircBuf = pStream->State.pCircBuf; 2174 AssertPtr(pCircBuf); 2202 2175 2203 2176 int rc = VINF_SUCCESS; 2204 2177 2205 Log3Func(("[SD%RU8] cbToProcess =%RU32, cbLeft=%RU32\n", pStream->u8SD, cbToProcess, cbLeft));2178 Log3Func(("[SD%RU8] cbToProcessMax=%RU32, cbLeft=%RU32\n", pStream->u8SD, cbToProcessMax, cbLeft)); 2206 2179 2207 2180 while (cbLeft) … … 2234 2207 } 2235 2208 2236 cbChunk = RT_MIN((uint32_t)(pRegs->picb << 1), cbLeft); /** @todo r=andy Assumes 16bit samples. */2209 uint32_t cbChunk = RT_MIN((uint32_t)(pRegs->picb << 1), cbLeft); /** @todo r=andy Assumes 16bit samples. */ 2237 2210 Assert(cbChunk); 2238 2211 … … 2241 2214 case AC97SOUNDSOURCE_PO_INDEX: /* Output */ 2242 2215 { 2243 PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), pRegs->bd.addr, 2244 (uint8_t *)pvBuf + cbTotal, cbChunk); 2216 void *pvDst; 2217 size_t cbDst; 2218 2219 RTCircBufAcquireWriteBlock(pCircBuf, cbChunk, &pvDst, &cbDst); 2220 2221 if (cbDst) 2222 { 2223 int rc2 = PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), pRegs->bd.addr, (uint8_t *)pvDst, cbDst); 2224 AssertRC(rc2); 2225 2226 #ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA 2227 RTFILE fh; 2228 RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "ac97DMARead.pcm", 2229 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); 2230 RTFileWrite(fh, pvDst, cbDst, NULL); 2231 RTFileClose(fh); 2232 #endif 2233 } 2234 2235 RTCircBufReleaseWriteBlock(pCircBuf, cbDst); 2236 2237 cbChunk = (uint32_t)cbDst; /* Update the current chunk size to what really has been written. */ 2245 2238 break; 2246 2239 } … … 2249 2242 case AC97SOUNDSOURCE_MC_INDEX: /* Input */ 2250 2243 { 2251 PDMDevHlpPhysWrite(pThis->CTX_SUFF(pDevIns), pRegs->bd.addr, 2252 (uint8_t *)pvBuf + cbTotal, cbChunk); 2244 void *pvSrc; 2245 size_t cbSrc; 2246 2247 RTCircBufAcquireReadBlock(pCircBuf, cbChunk, &pvSrc, &cbSrc); 2248 2249 if (cbSrc) 2250 { 2251 int rc2 = PDMDevHlpPhysWrite(pThis->CTX_SUFF(pDevIns), pRegs->bd.addr, (uint8_t *)pvSrc, cbSrc); 2252 AssertRC(rc2); 2253 2254 #ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA 2255 RTFILE fh; 2256 RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "ac97DMAWrite.pcm", 2257 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); 2258 RTFileWrite(fh, pvSrc, cbSrc, NULL); 2259 RTFileClose(fh); 2260 #endif 2261 } 2262 2263 RTCircBufReleaseReadBlock(pCircBuf, cbSrc); 2264 2265 cbChunk = (uint32_t)cbSrc; /* Update the current chunk size to what really has been read. */ 2253 2266 break; 2254 2267 } … … 2263 2276 break; 2264 2277 2265 #ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA2266 RTFILE fh;2267 RTFileOpen(&fh,2268 pStream->u8SD == AC97SOUNDSOURCE_PO_INDEX2269 ? VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "ac97DMARead.pcm" : VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "ac97DMAWrite.pcm",2270 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);2271 RTFileWrite(fh, (uint8_t *)pvBuf + cbTotal, cbChunk, NULL);2272 RTFileClose(fh);2273 #endif2274 2275 2278 if (cbChunk) 2276 2279 { 2277 cb Total += cbChunk;2278 Assert(cb Total <= cbToProcess);2280 cbProcessedTotal += cbChunk; 2281 Assert(cbProcessedTotal <= cbToProcessMax); 2279 2282 Assert(cbLeft >= cbChunk); 2280 2283 cbLeft -= cbChunk; … … 2286 2289 2287 2290 LogFlowFunc(("[SD%RU8]: cbChunk=%RU32, cbLeft=%RU32, cbTotal=%RU32, rc=%Rrc\n", 2288 pStream->u8SD, cbChunk, cbLeft, cb Total, rc));2291 pStream->u8SD, cbChunk, cbLeft, cbProcessedTotal, rc)); 2289 2292 2290 2293 if (!pRegs->picb) … … 2324 2327 break; 2325 2328 } 2326 }2327 2328 if (RT_SUCCESS(rc))2329 {2330 if (pcbProcessed)2331 *pcbProcessed = cbTotal;2332 2329 } 2333 2330
注意:
瀏覽 TracChangeset
來幫助您使用更動檢視器