📄 strmctxt.cpp
字号:
if (nVolumeRight > 0) {
nGainRight= scale_factor * (WAVE_VOLUME_MAX - nVolumeRight) / WAVE_VOLUME_MAX;
}
else {
nGainRight = VOLUME_MIN;
}
DEBUGMSG(ZONE_VOLUME, (TEXT("CStreamContext::SetVolume(%08x) -> %5d, %5d\r\n"), ulVolume, nGainRight, nGainLeft));
USHORT usLeftVal, usRightVal;
// Convert Decibels to Amplification factor and scale to suit SRC register
// [0 - 0xFFFF] scaled to [0 - 0x1000]
// The Ensoniq SRC volume registers represent a 4.12 fixed-point scale factor.
// In our case, we never amplify, only attenuate, so the largest scale factor
// we'll ever use is 0x1000, which corresponds to 1.0.
// A value of 0x800 corresponds to a scale factor of 0.5.
usRightVal = (USHORT) (DBToAmpFactor(nGainRight) >> 4);
usLeftVal = (USHORT) (DBToAmpFactor(nGainLeft) >> 4);
DEBUGMSG(ZONE_VOLUME, (TEXT("CStreamContext::SetVolume(%08x) => %04x,%04x\r\n"), ulVolume, usLeftVal,usRightVal));
m_pDevice->SetDMAVolume (m_ulDmaChannel, usLeftVal, usRightVal);
return MMSYSERR_NOERROR;
}
MMRESULT
CStreamContext::PrepareHeader (LPWAVEHDR lpHeader)
{
_MYTHIS_CHECK(return MMSYSERR_INVALHANDLE);
// nothing to do
return MMSYSERR_NOERROR;
}
MMRESULT
CStreamContext::UnprepareHeader (LPWAVEHDR lpHeader)
{
_MYTHIS_CHECK(return MMSYSERR_INVALHANDLE);
// nothing to do
return MMSYSERR_NOERROR;
}
// transfers queued data into the directsound buffer
// advances the write cursor
// If we don't have enough data queued up, we pad with silence, but only advance the
// transfer cursor as far as the real data goes.
VOID
COutputStreamContext::DoTransfer (PBYTE pDstBuffer, DWORD dwDstBytes)
{ PBYTE pSrcBuffer;
ULONG ulSrcBytes;
while (dwDstBytes > 0 && m_HdrQ.GetNextBlock (dwDstBytes, &pSrcBuffer, &ulSrcBytes)) {
ASSERT(ulSrcBytes <= dwDstBytes);
memcpy (pDstBuffer, pSrcBuffer, ulSrcBytes);
pDstBuffer += ulSrcBytes;
dwDstBytes -= ulSrcBytes;
m_dwTransferCursor += ulSrcBytes;
}
// check for fill cursor at end of buffer and wrap
ASSERT(m_dwTransferCursor <= m_dwBufferSize);
if (m_dwTransferCursor == m_dwBufferSize) {
m_dwTransferCursor = 0;
}
}
// the symmetric opposite of COutputStreamContext::DoTransfer
// Copies data FROM the buffer TO the Header Queue
void CInputStreamContext::DoTransfer (PBYTE pSrcBuffer, DWORD dwSrctBytes)
{ PBYTE pDstBuffer;
ULONG ulDstBytes;
while (dwSrctBytes > 0 && m_HdrQ.GetNextBlock (dwSrctBytes, &pDstBuffer, &ulDstBytes)) {
memcpy (pDstBuffer, pSrcBuffer, ulDstBytes);
pSrcBuffer += ulDstBytes;
dwSrctBytes -= ulDstBytes;
m_dwTransferCursor += ulDstBytes;
}
// when there is more capture data than fits in the Header Queue, we drop it immediately
// check for fill cursor at end of buffer and wrap
ASSERT(m_dwTransferCursor <= m_dwBufferSize);
if (m_dwTransferCursor == m_dwBufferSize) {
m_dwTransferCursor = 0;
}
}
// Transfer data to/from the DMA buffer and the WAVEHDR buffer.
// Start at the current m_dwTransferCursor and transfer dwTransferSize bytes.
// Handle the wrap-around case using the buffer lock mechanism
// Uses the virtual DoTransfer function to handle both input and output transfers
VOID
CStreamContext::DoSplitTransfer(DWORD dwTransferSize)
{ DWORD dwCount;
DWORD dwBytesLeft;
PBYTE pBuffer;
ASSERT(dwTransferSize <= m_dwBufferSize);
// we want to transfer dwTransferSize, but we need to figure out the address within the DMA buffer
dwBytesLeft = m_dwBufferSize - m_dwTransferCursor;
pBuffer = m_pBufferBits + m_dwTransferCursor;
dwCount = dwTransferSize;
if (dwCount > dwBytesLeft) {
dwCount -= dwBytesLeft;
// transfer to the end of the DMA buffer, then
// wrap around to the start of the buffer
DoTransfer(pBuffer, dwBytesLeft);
pBuffer = m_pBufferBits;
}
DoTransfer(pBuffer, dwCount);
}
VOID
CStreamContext::Stop(void)
{
if (m_fStarted) {
m_pDevice->StopDMAChannel( m_ulDmaChannel );
m_fStarted = FALSE;
}
}
VOID
CStreamContext::Start(void)
{
ASSERT(!m_fStarted);
ASSERT(!m_HdrQ.IsEmpty()); // not true for capture streams
// initialize the current DMA position and window
m_dwPreTime = GetTickCount();
m_dwTransferCursor = 0;
m_dwLastDMACursor = 0;
if (m_fIsPlayback) {
// output streams start off with a full-buffer transfer
// input streams must wait for data to accumulate in DMA bufffer before first transfer
DoSplitTransfer(m_dwBufferSize);
}
m_fStarted = TRUE;
m_pDevice->SetDMAPosition( m_ulDmaChannel, 0);
m_pDevice->SetDMAChannelBuffer( m_ulDmaChannel, m_dwBufferSize, (USHORT) m_dwInterruptInterval/m_wfx.nBlockAlign);
m_pDevice->StartDMAChannel( m_ulDmaChannel );
}
VOID
COutputStreamContext::GetAdvance(DWORD & dwAdvance, DWORD & dwRoomInBuffer)
{
// Reading the DMA position in a way that completely excludes the possibility
// of race conditions is an exercise in surreal temporal mechanics.
// Strictly speaking, one can never know for sure where the DMA position is, because
// an arbitrary amount of time may have elapsed since we actually looked at the hardware.
// By the same reasoning, it's impossible to know precisely what the current time is.
// However, one can get a grip on reality by reading the time before and after
// reading the DMA position, thereby ensuring that we at least have window
// of time during which we know the DMA position was valid.
DWORD dwPreTime = GetTickCount();
DWORD dwDMACursor = m_pDevice->GetDMAPosition(m_ulDmaChannel);
ASSERT(dwDMACursor <= m_dwBufferSize);
DWORD dwPostTime = GetTickCount();
// can't advance past the transfer cursor
DWORD dwMaxAdvance;
if (m_dwLastDMACursor < m_dwTransferCursor) {
dwMaxAdvance = m_dwTransferCursor - m_dwLastDMACursor;
}
else {
dwMaxAdvance = m_dwBufferSize + m_dwTransferCursor - m_dwLastDMACursor;
}
// determine how much the DMA position has advanced
// first, compute the worst-case elapsed time since we last read the DMA position
DWORD dwElapsed = dwPostTime - m_dwPreTime;
DWORD dwOldLast = m_dwLastDMACursor;
// assume we're safe & that there hasn't been enough time to wrap.
if (dwDMACursor >= m_dwLastDMACursor) {
dwAdvance = dwDMACursor - m_dwLastDMACursor;
}
else {
dwAdvance = m_dwBufferSize + dwDMACursor- m_dwLastDMACursor;
}
if ((dwElapsed >= m_dwBufferWrapTime-1) || (dwAdvance > dwMaxAdvance)) {
// we know for sure we've wrapped...
// or that the dma cursor advanced past the transfer cursor.
// either way, limit the advance to the end of cued data, mark the entire buffer as available
// DEBUGMSG(ZONE_WARNING, (TEXT("GetAdvance: underflow %3d %3d\r\n"), dwElapsed, m_dwBufferWrapTime));
dwAdvance = dwMaxAdvance; // we consumed all their was to consume
dwRoomInBuffer = m_dwBufferSize; // we can next consume the entire buffer
m_dwTransferCursor = dwDMACursor; // but start at where the DMA cursor is *now*
}
else {
// but how much room is there in the buffer?
DWORD dwBytesBeforeUnderflow = dwMaxAdvance - dwAdvance;
// if we're close to underflowing (i.e. no fresh data from stream source)
// we may want to silence-pad beyond the transfer cursor to avoid playing garbage
if (m_fIsPlayback && dwBytesBeforeUnderflow <= m_dwInterruptInterval) {
// clear enough of the buffer with silence to get us through the next interrupt
// worry about wrap-around, though
if (m_dwTransferCursor + m_dwInterruptInterval <= m_dwBufferSize) {
// safely do it on one piece
memset(m_pBufferBits + m_dwTransferCursor, m_ucSilence, m_dwInterruptInterval);
}
else {
// must wrap around the end of the buffer
memset(m_pBufferBits + m_dwTransferCursor, m_ucSilence, m_dwBufferSize - m_dwTransferCursor);
memset(m_pBufferBits , m_ucSilence, m_dwTransferCursor + m_dwInterruptInterval - m_dwBufferSize);
}
}
// finally, compute the amount of room left in the buffer for fresh data
dwRoomInBuffer = m_dwBufferSize - dwBytesBeforeUnderflow;
}
m_dwPreTime = dwPreTime;
m_dwLastDMACursor = dwDMACursor;
if (dwAdvance > m_dwBufferSize) {
RETAILMSG(1, (TEXT("Ensoniq: Over-advance! %08x %08x %08x\r\n"), dwAdvance, dwDMACursor, dwOldLast));
}
}
VOID
CInputStreamContext::GetAdvance(DWORD & dwAdvance)
{
DWORD dwPreTime = GetTickCount();
DWORD dwDMACursor = m_pDevice->GetDMAPosition(m_ulDmaChannel);
ASSERT(dwDMACursor <= m_dwBufferSize);
DWORD dwPostTime = GetTickCount();
// determine how much the DMA position has advanced
// first, compute the worst-case elapsed time since we last read the DMA position
DWORD dwElapsed = dwPostTime - m_dwPreTime;
DWORD dwOldLast = m_dwLastDMACursor;
if (dwElapsed >= m_dwBufferWrapTime-1) {
// we know for sure we've wrapped...
// or that the dma cursor advanced past the transfer cursor.
// either way, limit the advance to the end of cued data, mark the entire buffer as available
DEBUGMSG(ZONE_WARNING, (TEXT("GetAdvance: underflow %3d %3d\r\n"), dwElapsed, m_dwBufferWrapTime));
dwAdvance = m_dwBufferSize; // we can next consume the entire buffer
m_dwTransferCursor = dwDMACursor; // but start at where the DMA cursor is *now*
}
else {
// we're safe & that there hasn't been enough time to wrap.
if (dwDMACursor >= m_dwLastDMACursor) {
dwAdvance = dwDMACursor - m_dwLastDMACursor;
}
else {
dwAdvance = m_dwBufferSize + dwDMACursor- m_dwLastDMACursor;
}
}
m_dwPreTime = dwPreTime;
m_dwLastDMACursor = dwDMACursor;
if (dwAdvance > m_dwBufferSize) {
RETAILMSG(1, (TEXT("Ensoniq: Over-advance! %08x %08x %08x\r\n"), dwAdvance, dwDMACursor, dwOldLast));
}
}
VOID
CStreamContext::InterruptHandler(PVOID pArg)
{
CStreamContext * pthis = (CStreamContext *) pArg;
// addref/release around interrupt handler to avoid having the
// object pulled out from under the interrupt thread
pthis->AddRef();
pthis->HandleInterrupt();
pthis->Release();
}
void
COutputStreamContext::HandleInterrupt(void)
{
CAutoLock cal(&m_cs);
DWORD dwAdvance, dwRoomInBuffer;
GetAdvance(dwAdvance, dwRoomInBuffer);
// notify the Header Queue of the advance so it can (possibly) make callbacks.
m_HdrQ.AdvanceCurrentPosition(dwAdvance);
if (m_HdrQ.IsDone()) {
// finally, if the header queue is played out and we've played past the last known fill cursor position,
// we can stop the buffer and mark this stream as not running
Stop();
}
else {
DoSplitTransfer(dwRoomInBuffer); // transfer fresh data from the header queue into the DMA buffer
}
}
void
CInputStreamContext::HandleInterrupt(void)
{
CAutoLock cal(&m_cs);
DWORD dwAdvance;
GetAdvance(dwAdvance);
DoSplitTransfer(dwAdvance); // transfer freshly captured data into WAVEHDR buffers
// notify the Header Queue of the advance so it can (possibly) make callbacks.
m_HdrQ.AdvanceCurrentPosition(dwAdvance);
if (m_HdrQ.IsDone()) {
// finally, if the header queue is played out and we've played past the last known fill cursor position,
// we can stop the buffer and mark this stream as not running
Stop();
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -