VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostValidationKit.cpp@ 65606

Last change on this file since 65606 was 65606, checked in by vboxsync, 8 years ago

Audio/ValKit: Saving buffer submittions timings for each .wav file, bugref:7673

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.2 KB
Line 
1/* $Id: DrvHostValidationKit.cpp 65606 2017-02-03 19:00:44Z vboxsync $ */
2/** @file
3 * Validation Kit audio driver.
4 */
5
6/*
7 * Copyright (C) 2016-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 * --------------------------------------------------------------------
17 */
18
19#include <iprt/alloc.h>
20#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */
21
22#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
23#include <VBox/log.h>
24#include <VBox/vmm/pdmaudioifs.h>
25
26#include "DrvAudio.h"
27#include "VBoxDD.h"
28
29
30/**
31 * Structure for keeping a debug input/output stream.
32 */
33typedef struct VAKITAUDIOSTREAM
34{
35 /** Note: Always must come first! */
36 PDMAUDIOSTREAM Stream;
37 /** Audio file to dump output to or read input from. */
38 PDMAUDIOFILE File;
39 /** Text file to store timing of audio buffers submittions**/
40 RTFILE hFileTiming;
41 /** Timestamp of the first play or record request**/
42 uint64_t tsStarted;
43 /** Total number of samples played or recorded so far**/
44 uint32_t uSamplesSinceStarted;
45 union
46 {
47 struct
48 {
49 /** Timestamp of last captured samples. */
50 uint64_t tsLastCaptured;
51 } In;
52 struct
53 {
54 /** Timestamp of last played samples. */
55 uint64_t tsLastPlayed;
56 uint64_t cMaxSamplesInPlayBuffer;
57 uint8_t *pu8PlayBuffer;
58 } Out;
59 };
60
61} VAKITAUDIOSTREAM, *PVAKITAUDIOSTREAM;
62
63/**
64 * Validation Kit audio driver instance data.
65 * @implements PDMIAUDIOCONNECTOR
66 */
67typedef struct DRVHOSTVAKITAUDIO
68{
69 /** Pointer to the driver instance structure. */
70 PPDMDRVINS pDrvIns;
71 /** Pointer to host audio interface. */
72 PDMIHOSTAUDIO IHostAudio;
73} DRVHOSTVAKITAUDIO, *PDRVHOSTVAKITAUDIO;
74
75/*******************************************PDM_AUDIO_DRIVER******************************/
76
77
78/**
79 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
80 */
81static DECLCALLBACK(int) drvHostVaKitAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
82{
83 NOREF(pInterface);
84 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
85
86 pBackendCfg->cbStreamOut = sizeof(VAKITAUDIOSTREAM);
87 pBackendCfg->cbStreamIn = sizeof(VAKITAUDIOSTREAM);
88
89 pBackendCfg->cMaxStreamsOut = 1; /* Output */
90 pBackendCfg->cMaxStreamsIn = 2; /* Line input + microphone input. */
91
92 return VINF_SUCCESS;
93}
94
95
96/**
97 * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
98 */
99static DECLCALLBACK(int) drvHostVaKitAudioInit(PPDMIHOSTAUDIO pInterface)
100{
101 NOREF(pInterface);
102
103 LogFlowFuncLeaveRC(VINF_SUCCESS);
104 return VINF_SUCCESS;
105}
106
107
108/**
109 * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
110 */
111static DECLCALLBACK(void) drvHostVaKitAudioShutdown(PPDMIHOSTAUDIO pInterface)
112{
113 NOREF(pInterface);
114}
115
116
117/**
118 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
119 */
120static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostVaKitAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
121{
122 RT_NOREF(enmDir);
123 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
124
125 return PDMAUDIOBACKENDSTS_RUNNING;
126}
127
128
129static int debugCreateStreamIn(PPDMIHOSTAUDIO pInterface,
130 PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
131{
132 RT_NOREF(pInterface, pStream);
133
134 /* Just adopt the wanted stream configuration. */
135 PDMAUDIOPCMPROPS Props;
136 int rc = DrvAudioHlpStreamCfgToProps(pCfgReq, &Props);
137 if (RT_SUCCESS(rc))
138 {
139 if (pCfgAcq)
140 pCfgAcq->cSampleBufferSize = _1K;
141 }
142
143 LogFlowFuncLeaveRC(rc);
144 return rc;
145}
146
147
148static int debugCreateStreamOut(PPDMIHOSTAUDIO pInterface,
149 PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
150{
151 NOREF(pInterface);
152
153 PVAKITAUDIOSTREAM pDbgStream = (PVAKITAUDIOSTREAM)pStream;
154
155 /* Just adopt the wanted stream configuration. */
156 PDMAUDIOPCMPROPS Props;
157 int rc = DrvAudioHlpStreamCfgToProps(pCfgReq, &Props);
158 if (RT_SUCCESS(rc))
159 {
160 pDbgStream->tsStarted = 0;
161 pDbgStream->uSamplesSinceStarted = 0;
162 pDbgStream->Out.tsLastPlayed = 0;
163 pDbgStream->Out.cMaxSamplesInPlayBuffer = 16 * _1K;
164 pDbgStream->Out.pu8PlayBuffer = (uint8_t *)RTMemAlloc(pDbgStream->Out.cMaxSamplesInPlayBuffer << Props.cShift);
165 if (!pDbgStream->Out.pu8PlayBuffer)
166 rc = VERR_NO_MEMORY;
167 }
168
169 if (RT_SUCCESS(rc))
170 {
171 char szTemp[RTPATH_MAX];
172 rc = RTPathTemp(szTemp, sizeof(szTemp));
173
174 RTPathAppend(szTemp, sizeof(szTemp), "VBoxAudioValKit");
175
176 if (RT_SUCCESS(rc))
177 {
178 char szFile[RTPATH_MAX];
179 rc = DrvAudioHlpGetFileName(szFile, RT_ELEMENTS(szFile), szTemp, NULL, PDMAUDIOFILETYPE_WAV);
180 if (RT_SUCCESS(rc))
181 {
182 LogFlowFunc(("%s\n", szFile));
183 rc = DrvAudioHlpWAVFileOpen(&pDbgStream->File, szFile,
184 RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
185 &Props, PDMAUDIOFILEFLAG_NONE);
186 if (RT_FAILURE(rc))
187 LogRel(("DebugAudio: Creating output file '%s' failed with %Rrc\n", szFile, rc));
188
189 RTStrCat(szFile, sizeof(szFile), ".timing");
190 rc = RTFileOpen(&pDbgStream->hFileTiming, szFile, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE);
191
192
193 if (RT_FAILURE(rc))
194 LogRel(("DebugAudio: Creating output file '%s' failed with %Rrc\n", szFile, rc));
195 }
196 else
197 LogRel(("DebugAudio: Unable to build file name for temp dir '%s': %Rrc\n", szTemp, rc));
198 }
199 else
200 LogRel(("DebugAudio: Unable to retrieve temp dir: %Rrc\n", rc));
201 }
202
203 if (RT_SUCCESS(rc))
204 {
205 if (pCfgAcq)
206 pCfgAcq->cSampleBufferSize = pDbgStream->Out.cMaxSamplesInPlayBuffer;
207 }
208
209 LogFlowFuncLeaveRC(rc);
210 return rc;
211}
212
213
214/**
215 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
216 */
217static DECLCALLBACK(int) drvHostVaKitAudioStreamCreate(PPDMIHOSTAUDIO pInterface,
218 PPDMAUDIOSTREAM pStream,
219 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
220{
221 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
222 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
223 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
224
225 int rc;
226 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
227 rc = debugCreateStreamIn( pInterface, pStream, pCfgReq, pCfgAcq);
228 else
229 rc = debugCreateStreamOut(pInterface, pStream, pCfgReq, pCfgAcq);
230
231 LogFlowFunc(("%s: rc=%Rrc\n", pStream->szName, rc));
232 return rc;
233}
234
235
236/**
237 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
238 */
239static DECLCALLBACK(int) drvHostVaKitAudioStreamPlay(PPDMIHOSTAUDIO pInterface,
240 PPDMAUDIOSTREAM pStream, const void *pvBuf, uint32_t cbBuf,
241 uint32_t *pcbWritten)
242{
243 RT_NOREF(pvBuf, cbBuf);
244
245 PDRVHOSTVAKITAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio);
246 PVAKITAUDIOSTREAM pDbgStream = (PVAKITAUDIOSTREAM)pStream;
247
248 /* Consume as many samples as would be played at the current frequency since last call. */
249 /*uint32_t cLive = AudioMixBufLive(&pStream->MixBuf);*/
250
251 uint64_t u64TicksNow = PDMDrvHlpTMGetVirtualTime(pDrv->pDrvIns);
252 // uint64_t u64TicksElapsed = u64TicksNow - pDbgStream->Out.tsLastPlayed;
253 // uint64_t u64TicksFreq = PDMDrvHlpTMGetVirtualFreq(pDrv->pDrvIns);
254
255 /*
256 * Minimize the rounding error by adding 0.5: samples = int((u64TicksElapsed * samplesFreq) / u64TicksFreq + 0.5).
257 * If rounding is not taken into account then the playback rate will be consistently lower that expected.
258 */
259 // uint64_t cSamplesPlayed = (2 * u64TicksElapsed * pStream->Props.uHz + u64TicksFreq) / u64TicksFreq / 2;
260
261 /* Don't play more than available. */
262 /*if (cSamplesPlayed > cLive)
263 cSamplesPlayed = cLive;*/
264
265 uint64_t tsSinceStart;
266 size_t cch;
267 char szTimingInfo[128];
268
269 if (pDbgStream->tsStarted == 0)
270 {
271 pDbgStream->tsStarted = RTTimeNanoTS();
272 tsSinceStart = 0;
273 }
274 else
275 {
276 tsSinceStart = RTTimeNanoTS() - pDbgStream->tsStarted;
277 }
278
279 uint32_t uSamplesReady = AudioMixBufUsed(&pStream->MixBuf);
280 cch = RTStrPrintf(szTimingInfo, sizeof(szTimingInfo), "%d %d %d %d\n",
281 // Host time (in mcs) elapsed since Guest submitted the first buffer for playback
282 (uint32_t)(tsSinceStart / 1000),
283 // how long (in mcs) all the samples submitted previously were played
284 (uint32_t)(pDbgStream->uSamplesSinceStarted * 1.0E6 / pStream->Cfg.uHz),
285 // how long (in mcs) a new uSamplesReady samples should\will be played
286 (uint32_t)(uSamplesReady * 1.0E6 / pStream->Cfg.uHz),
287 uSamplesReady);
288 RTFileWrite(pDbgStream->hFileTiming, szTimingInfo, cch, NULL);
289 pDbgStream->uSamplesSinceStarted += uSamplesReady;
290
291 uint32_t cSamplesPlayed = 0;
292 uint32_t cSamplesAvail = RT_MIN(AudioMixBufUsed(&pStream->MixBuf), pDbgStream->Out.cMaxSamplesInPlayBuffer);
293
294 while (cSamplesAvail)
295 {
296 uint32_t cSamplesRead = 0;
297 int rc2 = AudioMixBufReadCirc(&pStream->MixBuf, pDbgStream->Out.pu8PlayBuffer,
298 AUDIOMIXBUF_S2B(&pStream->MixBuf, cSamplesAvail), &cSamplesRead);
299
300 if (RT_FAILURE(rc2))
301 LogRel(("DebugAudio: Reading output failed with %Rrc\n", rc2));
302
303 if (!cSamplesRead)
304 break;
305#if 0
306 RTFILE fh;
307 RTFileOpen(&fh, "/tmp/AudioDebug-Output.pcm",
308 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
309 RTFileWrite(fh, pDbgStream->Out.pu8PlayBuffer, AUDIOMIXBUF_S2B(&pStream->MixBuf, cSamplesRead), NULL);
310 RTFileClose(fh);
311#endif
312 rc2 = DrvAudioHlpWAVFileWrite(&pDbgStream->File,
313 pDbgStream->Out.pu8PlayBuffer, AUDIOMIXBUF_S2B(&pStream->MixBuf, cSamplesRead),
314 0 /* fFlags */);
315 if (RT_FAILURE(rc2))
316 LogRel(("DebugAudio: Writing output failed with %Rrc\n", rc2));
317
318 AudioMixBufFinish(&pStream->MixBuf, cSamplesRead);
319
320 Assert(cSamplesAvail >= cSamplesRead);
321 cSamplesAvail -= cSamplesRead;
322
323 cSamplesPlayed += cSamplesRead;
324 }
325
326 /* Remember when samples were consumed. */
327 pDbgStream->Out.tsLastPlayed = u64TicksNow;
328
329 if (pcbWritten)
330 *pcbWritten = cSamplesPlayed;
331
332 return VINF_SUCCESS;
333}
334
335
336/**
337 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
338 */
339static DECLCALLBACK(int) drvHostVaKitAudioStreamCapture(PPDMIHOSTAUDIO pInterface,
340 PPDMAUDIOSTREAM pStream, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
341{
342 RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
343
344 /* Never capture anything. */
345 if (pcbRead)
346 *pcbRead = 0;
347
348 return VINF_SUCCESS;
349}
350
351
352static int debugDestroyStreamIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOSTREAM pStream)
353{
354 RT_NOREF(pInterface, pStream);
355 LogFlowFuncLeaveRC(VINF_SUCCESS);
356 return VINF_SUCCESS;
357}
358
359
360static int debugDestroyStreamOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOSTREAM pStream)
361{
362 RT_NOREF(pInterface);
363 PVAKITAUDIOSTREAM pDbgStream = (PVAKITAUDIOSTREAM)pStream;
364 if ( pDbgStream
365 && pDbgStream->Out.pu8PlayBuffer)
366 {
367 RTMemFree(pDbgStream->Out.pu8PlayBuffer);
368 pDbgStream->Out.pu8PlayBuffer = NULL;
369 }
370
371 size_t cbDataSize = DrvAudioHlpWAVFileGetDataSize(&pDbgStream->File);
372
373 int rc = DrvAudioHlpWAVFileClose(&pDbgStream->File);
374
375 RTFileClose(pDbgStream->hFileTiming);
376
377 if (RT_SUCCESS(rc))
378 {
379 /* Delete the file again if nothing but the header was written to it. */
380 bool fDeleteEmptyFiles = true; /** @todo Make deletion configurable? */
381
382 if ( !cbDataSize
383 && fDeleteEmptyFiles)
384 {
385 char szFile[RTPATH_MAX];
386
387 RTStrCopy(szFile, sizeof(szFile), pDbgStream->File.szName);
388 rc = RTFileDelete(szFile);
389
390 RTStrCat(szFile, sizeof(szFile), ".timing");
391 rc = RTFileDelete(szFile);
392 }
393 else
394 LogRel(("DebugAudio: Created output file '%s' (%zu bytes)\n", pDbgStream->File.szName, cbDataSize));
395 }
396
397 LogFlowFuncLeaveRC(rc);
398 return rc;
399}
400
401
402static DECLCALLBACK(int) drvHostVaKitAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOSTREAM pStream)
403{
404 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
405 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
406
407 int rc;
408 if (pStream->enmDir == PDMAUDIODIR_IN)
409 rc = debugDestroyStreamIn(pInterface, pStream);
410 else
411 rc = debugDestroyStreamOut(pInterface, pStream);
412
413 return rc;
414}
415
416static DECLCALLBACK(int) drvHostVaKitAudioStreamControl(PPDMIHOSTAUDIO pInterface,
417 PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
418{
419 RT_NOREF(enmStreamCmd);
420
421 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
422 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
423
424 Assert(pStream->enmCtx == PDMAUDIOSTREAMCTX_HOST);
425
426 return VINF_SUCCESS;
427}
428
429static DECLCALLBACK(PDMAUDIOSTRMSTS) drvHostVaKitAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOSTREAM pStream)
430{
431 NOREF(pInterface);
432 NOREF(pStream);
433
434 return ( PDMAUDIOSTRMSTS_FLAG_INITIALIZED | PDMAUDIOSTRMSTS_FLAG_ENABLED
435 | PDMAUDIOSTRMSTS_FLAG_DATA_READABLE | PDMAUDIOSTRMSTS_FLAG_DATA_WRITABLE);
436}
437
438static DECLCALLBACK(int) drvHostVaKitAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOSTREAM pStream)
439{
440 NOREF(pInterface);
441 NOREF(pStream);
442
443 return VINF_SUCCESS;
444}
445
446
447/**
448 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
449 */
450static DECLCALLBACK(void *) drvHostVaKitAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
451{
452 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
453 PDRVHOSTVAKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVAKITAUDIO);
454
455 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
456 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
457 return NULL;
458}
459
460
461/**
462 * Constructs a Null audio driver instance.
463 *
464 * @copydoc FNPDMDRVCONSTRUCT
465 */
466static DECLCALLBACK(int) drvHostVaKitAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
467{
468 RT_NOREF(pCfg, fFlags);
469 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
470 PDRVHOSTVAKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVAKITAUDIO);
471 LogRel(("Audio: Initializing ValidationKit driver\n"));
472
473 /*
474 * Init the static parts.
475 */
476 pThis->pDrvIns = pDrvIns;
477 /* IBase */
478 pDrvIns->IBase.pfnQueryInterface = drvHostVaKitAudioQueryInterface;
479 /* IHostAudio */
480 PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostVaKitAudio);
481
482 return VINF_SUCCESS;
483}
484
485/**
486 * Char driver registration record.
487 */
488const PDMDRVREG g_DrvHostValidationKitAudio =
489{
490 /* u32Version */
491 PDM_DRVREG_VERSION,
492 /* szName */
493 "ValidationKitAudio",
494 /* szRCMod */
495 "",
496 /* szR0Mod */
497 "",
498 /* pszDescription */
499 "ValidationKit audio host driver",
500 /* fFlags */
501 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
502 /* fClass. */
503 PDM_DRVREG_CLASS_AUDIO,
504 /* cMaxInstances */
505 1,
506 /* cbInstance */
507 sizeof(DRVHOSTVAKITAUDIO),
508 /* pfnConstruct */
509 drvHostVaKitAudioConstruct,
510 /* pfnDestruct */
511 NULL,
512 /* pfnRelocate */
513 NULL,
514 /* pfnIOCtl */
515 NULL,
516 /* pfnPowerOn */
517 NULL,
518 /* pfnReset */
519 NULL,
520 /* pfnSuspend */
521 NULL,
522 /* pfnResume */
523 NULL,
524 /* pfnAttach */
525 NULL,
526 /* pfnDetach */
527 NULL,
528 /* pfnPowerOff */
529 NULL,
530 /* pfnSoftReset */
531 NULL,
532 /* u32EndVersion */
533 PDM_DRVREG_VERSION
534};
535
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette