VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/HDAStreamPeriod.cpp@ 67400

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

Audio/DevHDA: Forward ported / integrated wall clock handling + stream period code.

  • Property svn:executable set to *
File size: 13.4 KB
Line 
1/* $Id$ */
2/** @file
3 * HDAStreamPeriod.cpp - Stream period functions for HD Audio.
4 *
5 * Utility functions for handling HDA audio stream periods.
6 * Stream period handling is needed in order to keep track of a stream's timing
7 * and processed audio data.
8 *
9 * As the HDA device only has one bit clock (WALCLK) but audio streams can be processed
10 * at certain points in time, these functions can be used to estimate and schedule the
11 * wall clock (WALCLK) for all streams accordingly.
12 */
13
14/*
15 * Copyright (C) 2017 Oracle Corporation
16 *
17 * This file is part of VirtualBox Open Source Edition (OSE), as
18 * available from http://www.virtualbox.org. This file is free software;
19 * you can redistribute it and/or modify it under the terms of the GNU
20 * General Public License (GPL) as published by the Free Software
21 * Foundation, in version 2 as it comes in the "COPYING" file of the
22 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
23 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
24 */
25
26/*********************************************************************************************************************************
27* Header Files *
28*********************************************************************************************************************************/
29#define LOG_GROUP LOG_GROUP_DEV_HDA
30#include <VBox/log.h>
31
32#include <VBox/vmm/pdmdev.h>
33#include <VBox/vmm/pdmaudioifs.h>
34
35#include "HDAStreamPeriod.h"
36#include "DevHDA.h"
37
38#ifdef IN_RING3
39/**
40 * Creates a stream period.
41 *
42 * @return IPRT status code.
43 * @param pPeriod Stream period to initialize.
44 */
45int hdaStreamPeriodCreate(PHDASTREAMPERIOD pPeriod)
46{
47 Assert(!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_VALID));
48
49 int rc = RTCritSectInit(&pPeriod->CritSect);
50 if (RT_SUCCESS(rc))
51 {
52 pPeriod->fStatus = HDASTREAMPERIOD_FLAG_VALID;
53 }
54
55 return VINF_SUCCESS;
56}
57
58/**
59 * Destroys a formerly created stream period.
60 *
61 * @param pPeriod Stream period to destroy.
62 */
63void hdaStreamPeriodDestroy(PHDASTREAMPERIOD pPeriod)
64{
65 if (pPeriod->fStatus & HDASTREAMPERIOD_FLAG_VALID)
66 {
67 RTCritSectDelete(&pPeriod->CritSect);
68
69 pPeriod->fStatus = HDASTREAMPERIOD_FLAG_NONE;
70 }
71}
72
73/**
74 * Initializes a given stream period with needed parameters.
75 *
76 * @param pPeriod Stream period to (re-)initialize. Must be created with hdaStreamPeriodCreate() first.
77 * @param u8SD Stream descriptor (serial data #) number to assign this stream period to.
78 * @param u16LVI The HDA stream's LVI value to use for the period calculation.
79 * @param u32CBL The HDA stream's CBL value to use for the period calculation.
80 * @param pStreamCfg Audio stream configuration to use for this period.
81 */
82void hdaStreamPeriodInit(PHDASTREAMPERIOD pPeriod,
83 uint8_t u8SD, uint16_t u16LVI, uint32_t u32CBL, PPDMAUDIOSTREAMCFG pStreamCfg)
84{
85 /*
86 * Linux guests (at least Ubuntu):
87 * 17632 bytes (CBL) / 4 (frame size) = 4408 frames / 4 (LVI) = 1102 frames per period
88 *
89 * Windows guests (Win10 AU):
90 * 3584 bytes (CBL) / 4 (frame size) = 896 frames / 2 (LVI) = 448 frames per period
91 */
92 unsigned cTotalPeriods = u16LVI;
93
94 if (cTotalPeriods <= 1)
95 cTotalPeriods = 2; /* At least two periods *must* be present (LVI >= 1). */
96
97 uint32_t framesToTransfer = (u32CBL / 4 /** @todo Define frame size? */) / cTotalPeriods;
98
99 pPeriod->u8SD = u8SD;
100 pPeriod->u64StartWalClk = 0;
101 pPeriod->u32Hz = pStreamCfg->Props.uHz;
102 pPeriod->u64DurationWalClk = hdaStreamPeriodFramesToWalClk(pPeriod, framesToTransfer);
103 pPeriod->u64ElapsedWalClk = 0;
104 pPeriod->i64DelayWalClk = 0;
105 pPeriod->framesToTransfer = framesToTransfer;
106 pPeriod->framesTransferred = 0;
107 pPeriod->cIntPending = 0;
108
109 Log3Func(("[SD%RU8] %RU64 long, Hz=%RU32, CBL=%RU32, LVI=%RU16 -> %u periods, %RU32 frames each\n",
110 pPeriod->u8SD, pPeriod->u64DurationWalClk, pPeriod->u32Hz, u32CBL, u16LVI,
111 cTotalPeriods, pPeriod->framesToTransfer));
112}
113
114/**
115 * Resets a stream period to its initial state.
116 *
117 * @param pPeriod Stream period to reset.
118 */
119void hdaStreamPeriodReset(PHDASTREAMPERIOD pPeriod)
120{
121 Log3Func(("[SD%RU8]\n", pPeriod->u8SD));
122
123 if (pPeriod->cIntPending)
124 LogFunc(("Warning: %RU8 interrupts for stream #%RU8 still pending -- so a period reset might trigger audio hangs\n",
125 pPeriod->cIntPending, pPeriod->u8SD));
126
127 pPeriod->fStatus &= ~HDASTREAMPERIOD_FLAG_ACTIVE;
128 pPeriod->u64StartWalClk = 0;
129 pPeriod->u64ElapsedWalClk = 0;
130 pPeriod->framesTransferred = 0;
131 pPeriod->cIntPending = 0;
132#ifdef DEBUG
133 pPeriod->Dbg.tsStartNs = 0;
134#endif
135}
136
137/**
138 * Begins a new period life span of a given period.
139 *
140 * @return IPRT status code.
141 * @param pPeriod Stream period to begin new life span for.
142 * @param u64WalClk Wall clock (WALCLK) value to set for the period's starting point.
143 */
144int hdaStreamPeriodBegin(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk)
145{
146 Assert(!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE)); /* No nested calls. */
147
148 pPeriod->fStatus |= HDASTREAMPERIOD_FLAG_ACTIVE;
149 pPeriod->u64StartWalClk = u64WalClk;
150 pPeriod->u64ElapsedWalClk = 0;
151 pPeriod->framesTransferred = 0;
152 pPeriod->cIntPending = 0;
153#ifdef DEBUG
154 pPeriod->Dbg.tsStartNs = RTTimeNanoTS();
155#endif
156
157 Log3Func(("[SD%RU8] Starting @ %RU64 (%RU64 long)\n",
158 pPeriod->u8SD, pPeriod->u64StartWalClk, pPeriod->u64DurationWalClk));
159
160 return VINF_SUCCESS;
161}
162
163/**
164 * Ends a formerly begun period life span.
165 *
166 * @param pPeriod Stream period to end life span for.
167 */
168void hdaStreamPeriodEnd(PHDASTREAMPERIOD pPeriod)
169{
170 Log3Func(("[SD%RU8] Took %zuus\n", pPeriod->u8SD, (RTTimeNanoTS() - pPeriod->Dbg.tsStartNs) / 1000));
171
172 if (!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE))
173 return;
174
175 /* Sanity. */
176 AssertMsg(pPeriod->cIntPending == 0,
177 ("%RU8 interrupts for stream #%RU8 still pending -- so ending a period might trigger audio hangs\n",
178 pPeriod->cIntPending, pPeriod->u8SD));
179 Assert(hdaStreamPeriodIsComplete(pPeriod));
180
181 pPeriod->fStatus &= ~HDASTREAMPERIOD_FLAG_ACTIVE;
182}
183
184/**
185 * Locks a stream period for serializing access.
186 *
187 * @return \c true if locking was successful, \c false if not.
188 * @param pPeriod Stream period to lock.
189 */
190bool hdaStreamPeriodLock(PHDASTREAMPERIOD pPeriod)
191{
192 return RT_SUCCESS(RTCritSectEnter(&pPeriod->CritSect));
193}
194
195/**
196 * Unlocks a formerly locked stream period.
197 *
198 * @param pPeriod Stream period to unlock.
199 */
200void hdaStreamPeriodUnlock(PHDASTREAMPERIOD pPeriod)
201{
202 int rc2 = RTCritSectLeave(&pPeriod->CritSect);
203 AssertRC(rc2);
204}
205
206/**
207 * Returns the wall clock (WALCLK) value for a given amount of stream period audio frames.
208 *
209 * @return Calculated wall clock value.
210 * @param pPeriod Stream period to calculate wall clock value for.
211 * @param uFrames Number of audio frames to calculate wall clock value for.
212 *
213 * @remark Calculation depends on the given stream period and assumes a 24 MHz wall clock counter (WALCLK).
214 */
215uint64_t hdaStreamPeriodFramesToWalClk(PHDASTREAMPERIOD pPeriod, uint32_t uFrames)
216{
217 /* Prevent division by zero. */
218 const uint32_t uHz = (pPeriod->u32Hz ? pPeriod->u32Hz : 1);
219
220 /* 24 MHz WallClock (WALCLK): 42ns resolution. */
221 return ((uFrames * 24000 /* 24 MHz */) / uHz) * 1000;
222}
223
224/**
225 * Returns the absolute wall clock (WALCLK) value for the already elapsed time of
226 * a given stream period.
227 *
228 * @return Absolute elapsed time as wall clock (WALCLK) value.
229 * @param pPeriod Stream period to use.
230 */
231uint64_t hdaStreamPeriodGetAbsElapsedWalClk(PHDASTREAMPERIOD pPeriod)
232{
233 return pPeriod->u64StartWalClk
234 + pPeriod->u64ElapsedWalClk
235 + pPeriod->i64DelayWalClk;
236}
237
238/**
239 * Returns the absolute wall clock (WALCLK) value for the calculated end time of
240 * a given stream period.
241 *
242 * @return Absolute end time as wall clock (WALCLK) value.
243 * @param pPeriod Stream period to use.
244 */
245uint64_t hdaStreamPeriodGetAbsEndWalClk(PHDASTREAMPERIOD pPeriod)
246{
247 return pPeriod->u64StartWalClk + pPeriod->u64DurationWalClk;
248}
249
250/**
251 * Returns the remaining audio frames to process for a given stream period.
252 *
253 * @return Number of remaining audio frames to process. 0 if all were processed.
254 * @param pPeriod Stream period to return value for.
255 */
256uint32_t hdaStreamPeriodGetRemainingFrames(PHDASTREAMPERIOD pPeriod)
257{
258 Assert(pPeriod->framesToTransfer >= pPeriod->framesTransferred);
259 return pPeriod->framesToTransfer - pPeriod->framesTransferred;
260}
261
262/**
263 * Tells whether a given stream period has elapsed (time-wise) or not.
264 *
265 * @return \c true if the stream period has elapsed, \c false if not.
266 * @param pPeriod Stream period to get status for.
267 */
268bool hdaStreamPeriodHasElapsed(PHDASTREAMPERIOD pPeriod)
269{
270 return (pPeriod->u64ElapsedWalClk >= pPeriod->u64DurationWalClk);
271}
272
273/**
274 * Tells whether a given stream period has passed the given absolute wall clock (WALCLK)
275 * time or not
276 *
277 * @return \c true if the stream period has passed the given time, \c false if not.
278 * @param pPeriod Stream period to get status for.
279 * @param u64WalClk Absolute wall clock (WALCLK) time to check for.
280 */
281bool hdaStreamPeriodHasPassedAbsWalClk(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk)
282{
283 /* Period not in use? */
284 if (!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE))
285 return true; /* ... implies that it has passed. */
286
287 if (hdaStreamPeriodHasElapsed(pPeriod))
288 return true; /* Period already has elapsed. */
289
290 return (pPeriod->u64StartWalClk + pPeriod->u64ElapsedWalClk) >= u64WalClk;
291}
292
293/**
294 * Tells whether a given stream period has some required interrupts pending or not.
295 *
296 * @return \c true if period has interrupts pending, \c false if not.
297 * @param pPeriod Stream period to get status for.
298 */
299bool hdaStreamPeriodNeedsInterrupt(PHDASTREAMPERIOD pPeriod)
300{
301 return pPeriod->cIntPending > 0;
302}
303
304/**
305 * Acquires (references) an (pending) interrupt for a given stream period.
306 *
307 * @param pPeriod Stream period to acquire interrupt for.
308 *
309 * @remark This routine does not do any actual interrupt processing; it only
310 * keeps track of the required (pending) interrupts for a stream period.
311 */
312void hdaStreamPeriodAcquireInterrupt(PHDASTREAMPERIOD pPeriod)
313{
314 uint32_t cIntPending = pPeriod->cIntPending;
315 if (cIntPending)
316 {
317 Log3Func(("[SD%RU8] Already pending\n", pPeriod->u8SD));
318 return;
319 }
320
321 pPeriod->cIntPending++;
322
323 Log3Func(("[SD%RU8] %RU32\n", pPeriod->u8SD, pPeriod->cIntPending));
324}
325
326/**
327 * Releases (dereferences) a pending interrupt.
328 *
329 * @param pPeriod Stream period to release pending interrupt for.
330 */
331void hdaStreamPeriodReleaseInterrupt(PHDASTREAMPERIOD pPeriod)
332{
333 Assert(pPeriod->cIntPending);
334 pPeriod->cIntPending--;
335
336 Log3Func(("[SD%RU8] %RU32\n", pPeriod->u8SD, pPeriod->cIntPending));
337}
338
339/**
340 * Adds an amount of (processed) audio frames to a given stream period.
341 *
342 * @return IPRT status code.
343 * @param pPeriod Stream period to add audio frames to.
344 * @param framesInc Audio frames to add.
345 */
346void hdaStreamPeriodInc(PHDASTREAMPERIOD pPeriod, uint32_t framesInc)
347{
348 pPeriod->framesTransferred += framesInc;
349 Assert(pPeriod->framesTransferred <= pPeriod->framesToTransfer);
350
351 pPeriod->u64ElapsedWalClk = hdaStreamPeriodFramesToWalClk(pPeriod, pPeriod->framesTransferred);
352 Assert(pPeriod->u64ElapsedWalClk <= pPeriod->u64DurationWalClk);
353
354 Log3Func(("[SD%RU8] cbTransferred=%RU32, u64ElapsedWalClk=%RU64\n",
355 pPeriod->u8SD, pPeriod->framesTransferred, pPeriod->u64ElapsedWalClk));
356}
357
358/**
359 * Tells whether a given stream period is considered as complete or not.
360 *
361 * @return \c true if stream period is complete, \c false if not.
362 * @param pPeriod Stream period to report status for.
363 *
364 * @remark A stream period is considered complete if it has 1) passed (elapsed) its calculated period time
365 * and 2) processed all required audio frames.
366 */
367bool hdaStreamPeriodIsComplete(PHDASTREAMPERIOD pPeriod)
368{
369 const bool fIsComplete = /* Has the period elapsed time-wise? */
370 hdaStreamPeriodHasElapsed(pPeriod)
371 /* All frames transferred? */
372 && pPeriod->framesTransferred >= pPeriod->framesToTransfer;
373#ifdef VBOX_STRICT
374 if (fIsComplete)
375 {
376 Assert(pPeriod->framesTransferred == pPeriod->framesToTransfer);
377 Assert(pPeriod->u64ElapsedWalClk == pPeriod->u64DurationWalClk);
378 }
379#endif
380
381 Log3Func(("[SD%RU8] Period %s - runtime %RU64 / %RU64 (abs @ %RU64, starts @ %RU64, ends @ %RU64), %RU8 IRQs pending\n",
382 pPeriod->u8SD,
383 fIsComplete ? "COMPLETE" : "NOT COMPLETE YET",
384 pPeriod->u64ElapsedWalClk, pPeriod->u64DurationWalClk,
385 hdaStreamPeriodGetAbsElapsedWalClk(pPeriod), pPeriod->u64StartWalClk,
386 hdaStreamPeriodGetAbsEndWalClk(pPeriod), pPeriod->cIntPending));
387
388 return fIsComplete;
389}
390#endif /* IN_RING3 */
391
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