📄 waveout_win32.c
字号:
/*****************************************************************************
*
* This program is free software ; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: waveout_win32.c 202 2005-01-25 01:27:33Z picard $
*
* BetaPlayer Core
* Copyright (c) 2004 Gabor Kovacs
*
****************************************************************************/
#include "../stdafx.h"
#include <math.h>
#if defined(_WIN32)
#ifndef STRICT
#define STRICT
#endif
#include <windows.h>
#define RAMPSIZE 15
#define BENCH_SIZE 32
struct waveout;
typedef struct buffer
{
WAVEHDR Head;
planes Planes;
struct buffer* Next; //next in chain
struct buffer* GlobalNext;
tick_t RefTime;
int Bytes;
char* Buffer;
} buffer;
typedef struct waveout
{
node VMT;
audio InputFormat;
audio OutputFormat;
buffer* Buffers; // global chain
buffer* FreeFirst;
buffer* FreeLast;
int BufferLength; // one buffer length (waveout format)
tick_t BufferScaledTime; // scaled time of one waveout buffer (BufferLength)
int BufferScaledAdjust; // waveout bytes to scaled time convert (12bit fixed point)
int BytesPerSample;
int TotalBytes; // source format
int Bytes; // output format
int FillPos;
int Skip;
buffer* FillFirst;
buffer* FillLast;
buffer** Pausing;
HWAVEOUT Handle;
CRITICAL_SECTION Section;
void* PCM;
int PCMSpeed;
int BufferLimit;
int BufferLimitFull;
tick_t Tick;
int TimeRef;
LONG Waiting; // number of waiting buffers in WaveOut
LONG Used; // number of used buffers (waiting or in fill chain)
int Play;
fraction Speed;
fraction SpeedTime;
int AdjustedRate;
bool_t Dither;
bool_t Swap;
int Stereo;
int Quality;
tick_t BufferTime;
bool_t SoftwareVolume;
bool_t MonoVol;
bool_t Mute; // just for caching
int Volume; // backup value when mute is turned on
int VolumeRamp;
int VolumeSoftLog;
WAVEFORMATEX Format;
int BenchWait[BENCH_SIZE];
int BenchSum[BENCH_SIZE];
int BenchCurrSum;
int BenchAvg;
int BenchAdj;
int BenchAvgLimit;
int BenchSpeedAvg;
int BenchWaitPos;
} waveout;
static int Enum( waveout* p, int No, datadef* Param )
{
return NodeEnumType(&No,Param,NodeParams,TimerParams,FlowParams,OutputParams,AOutputParams,NULL);
}
static int UpdateVolumeSoftLog(waveout* p)
{
int OldVolumeSoftLog = p->VolumeSoftLog;
if (p->SoftwareVolume)
p->VolumeSoftLog = (int)(pow(10,(50+GetSoftVolume())/62.3));
else
p->VolumeSoftLog = 256;
if (p->Handle && OldVolumeSoftLog)
{
buffer* List;
int Adjust = (p->VolumeSoftLog * 256)/OldVolumeSoftLog;
if (Adjust != 256)
{
void* PCM = PCMCreate(&p->OutputFormat,&p->OutputFormat,0,1);
void* Buffer = Alloc(p->BufferLength);
if (Buffer && PCM)
for (List=p->Buffers;List;List=List->GlobalNext)
{
int DstLength;
int SrcLength;
planes DstPlanes;
planes SrcPlanes;
DstLength = SrcLength = p->BufferLength;
SrcPlanes[0] = List->Buffer;
DstPlanes[0] = Buffer;
PCMConvert(PCM,DstPlanes,SrcPlanes,&DstLength,&SrcLength,SPEED_ONE,Adjust);
memcpy(List->Buffer,Buffer,p->BufferLength);
}
Free(Buffer);
PCMRelease(PCM);
}
}
return ERR_NONE;
}
static bool_t GetMute(waveout* p)
{
DWORD Value = 0;
waveOutGetVolume(NULL,&Value);
if (p->MonoVol) Value &= 0xFFFF;
p->Mute = Value==0;
return p->Mute;
}
static int GetVolume(waveout* p)
{
DWORD Value;
if (waveOutGetVolume(NULL,&Value) == MMSYSERR_NOERROR)
{
if (p->MonoVol)
{
Value &= 0xFFFF;
Value |= Value << 16;
}
p->Mute = Value==0;
if (!p->Mute)
p->Volume = ((LOWORD(Value)+HIWORD(Value)+600)*100) / (0xFFFF*2);
}
if (p->SoftwareVolume)
return GetSoftVolume();
return p->Volume;
}
static int Get( waveout* p, int No, void* Data, int Size )
{
int Result = ERR_INVALID_PARAM;
switch (No)
{
case NODE_ID: GETVALUE(WAVEOUT_ID,int); break;
case FLOW_EMPTY: GETVALUE(p->Waiting<=0,bool_t); break;
case AOUT_INPUT|PACKET_FORMAT: GETVALUE(p->InputFormat,audio); break;
case AOUT_OUTPUT|PACKET_FORMAT: GETVALUE(p->OutputFormat,audio); break;
case AOUT_VOLUME: GETVALUE(GetVolume(p),int); break;
case AOUT_MUTE: GETVALUE(GetMute(p),bool_t); break;
case AOUT_STEREO: GETVALUE(p->Stereo,int); break;
case AOUT_SWAP: GETVALUE(p->Swap,bool_t); break;
case AOUT_BUFFER: GETVALUE(p->BufferTime,tick_t); break;
case AOUT_QUALITY: GETVALUE(p->Quality,int); break;
case FLOW_TOTAL:GETVALUE(p->BytesPerSample ? (p->TotalBytes / p->BytesPerSample):0,int); break;
case TIMER_PLAY: GETVALUE(p->Play,bool_t); break;
case TIMER_SPEED: GETVALUE(p->Speed,fraction); break;
case TIMER_TIME:
if (Size == sizeof(tick_t))
{
tick_t t;
if (p->Speed.Num==0)
t = -1;
else
if (p->Play)
{
EnterCriticalSection(&p->Section);
t = p->Tick + Scale(GetTimeTick()-p->TimeRef,p->SpeedTime.Num,p->SpeedTime.Den);
LeaveCriticalSection(&p->Section);
}
else
t = p->Tick;
*(tick_t*)Data = t;
Result = ERR_NONE;
}
break;
}
return Result;
}
static void UpdateBenchAvg(waveout* p)
{
int n;
p->BenchAvg += 4;
p->BenchAdj = (24*16) / p->BenchAvg;
p->BenchSpeedAvg = SPEED_ONE/(p->BenchAvg*BENCH_SIZE);
n = p->BytesPerSample * p->OutputFormat.SampleRate;
if (n>0)
p->BenchAvgLimit = (n * p->BenchAvg) / 160;
else
p->BenchAvgLimit = MAX_INT;
p->BenchCurrSum += 2 * BENCH_SIZE;
for (n=0;n<BENCH_SIZE;++n)
{
p->BenchSum[n] += 2 * BENCH_SIZE;
p->BenchWait[n] += 2;
}
}
static void ReleaseBuffer(waveout* p,buffer* Buffer,bool_t Update)
{
EnterCriticalSection(&p->Section);
if (Update)
{
tick_t Old;
int Time = GetTimeTick();
Old = p->Tick + Scale(Time-p->TimeRef,p->SpeedTime.Num,p->SpeedTime.Den);
if (Buffer->RefTime >= 0)
p->Tick = Buffer->RefTime + p->BufferScaledTime;
else
p->Tick += p->BufferScaledTime;
p->TimeRef = Time;
//DEBUG_MSG2(-1,T("WaveOutTime: %d %d"),Old,p->Tick);
// if difference is little then just adjust (because GetTimeTick() is more linear)
if (abs(Old - p->Tick) < TICKSPERSEC/2)
p->Tick = Old + ((p->Tick - Old) >> 2);
}
Buffer->Next = NULL;
if (p->FreeLast)
p->FreeLast->Next = Buffer;
else
p->FreeFirst = Buffer;
p->FreeLast = Buffer;
p->Used--;
LeaveCriticalSection(&p->Section);
}
static void Reset(waveout* p)
{
int n;
// release buffers already sended to device
if (p->Handle)
{
tick_t OldTick = p->Tick;
waveOutReset(p->Handle);
p->Tick = OldTick;
}
// release fill chain
while (p->FillFirst)
{
buffer* Buffer = p->FillFirst;
p->FillFirst = Buffer->Next;
ReleaseBuffer(p,Buffer,0);
}
p->FillLast = NULL;
p->FillPos = p->BufferLength;
p->Skip = 0;
p->Bytes = 0;
p->BufferLimit = p->BufferLimitFull;
p->BenchAvg = 16-4;
UpdateBenchAvg(p);
p->BenchWaitPos = 0;
p->BenchCurrSum = p->BenchAvg * BENCH_SIZE;
for (n=0;n<BENCH_SIZE;++n)
{
p->BenchSum[n] = p->BenchCurrSum;
p->BenchWait[n] = p->BenchAvg;
}
PCMReset(p->PCM); // init dither and subsample position
}
static buffer* GetBuffer(waveout* p)
{
buffer* Buffer;
// try to find a free buffer
EnterCriticalSection(&p->Section);
Buffer = p->FreeFirst;
if (Buffer)
{
p->FreeFirst = Buffer->Next;
if (Buffer == p->FreeLast)
p->FreeLast = NULL;
}
if (!Buffer)
{
char* i = (char*) AllocBlock(p->BufferLength,p->Used>=20);
if (i)
{
Buffer = (buffer*) Alloc(sizeof(buffer));
if (!Buffer)
FreeBlock(i);
}
if (Buffer)
{
Buffer->Buffer = i;
Buffer->Planes[0] = Buffer->Buffer;
Buffer->Next = NULL;
memset(&Buffer->Head,0,sizeof(WAVEHDR));
Buffer->Head.lpData = Buffer->Buffer;
Buffer->Head.dwUser = (DWORD)Buffer;
Buffer->Head.dwBufferLength = p->BufferLength;
Buffer->Head.dwBytesRecorded = p->BufferLength;
if (waveOutPrepareHeader(p->Handle, &Buffer->Head, sizeof(WAVEHDR)) != MMSYSERR_NOERROR)
{
FreeBlock(Buffer->Buffer);
Free(Buffer);
Buffer = NULL;
}
else
{
Buffer->GlobalNext = p->Buffers;
p->Buffers = Buffer;
}
}
else
{
p->BufferLimitFull = p->Used;
if (p->BufferLimit > p->Used)
p->BufferLimit = p->Used;
else
if (p->BufferLimit > 4)
p->BufferLimit--;
}
}
if (Buffer)
{
p->Used++;
Buffer->RefTime = -1;
Buffer->Next =NULL;
}
LeaveCriticalSection(&p->Section);
return Buffer;
}
static void Write(waveout* p, tick_t CurrTime)
{
if (p->Play)
while (p->FillFirst != p->FillLast)
{
buffer* Buffer = p->FillFirst;
if (!p->Waiting && CurrTime >= 0 && Buffer->RefTime >= 0 && Buffer->RefTime > CurrTime + SHOWAHEAD)
break;
p->FillFirst = Buffer->Next;
if (waveOutWrite( p->Handle, &Buffer->Head, sizeof(WAVEHDR)) != MMSYSERR_NOERROR)
ReleaseBuffer(p,Buffer,0);
else
InterlockedIncrement(&p->Waiting);
}
}
static int Send(waveout* p, const planes Planes, int Length, tick_t RefTime, tick_t CurrTime, int Speed )
{
buffer* Buffer;
int DstLength;
int SrcLength;
planes DstPlanes;
planes SrcPlanes;
p->TotalBytes += Length;
SrcPlanes[0] = Planes[0];
SrcPlanes[1] = Planes[1];
if (p->Skip > 0)
{
SrcLength = p->Skip;
if (SrcLength > Length)
SrcLength = Length;
SrcPlanes[0] = (uint8_t*)SrcPlanes[0] + SrcLength;
SrcPlanes[1] = (uint8_t*)SrcPlanes[1] + SrcLength;
Length -= SrcLength;
}
while (Length > 0)
{
if (p->FillPos >= p->BufferLength)
{
// allocate new buffer
Buffer = GetBuffer(p);
if (!Buffer)
break;
if (p->FillLast)
p->FillLast->Next = Buffer;
else
p->FillFirst = Buffer;
p->FillLast = Buffer;
p->FillPos = 0;
Buffer->Bytes = p->Bytes;
}
else
Buffer = p->FillLast;
if (RefTime >= 0)
{
Buffer->RefTime = RefTime - ((p->FillPos * p->BufferScaledAdjust) >> 12);
RefTime = -1;
}
SrcLength = Length;
DstLength = p->BufferLength - p->FillPos;
DstPlanes[0] = Buffer->Buffer + p->FillPos;
PCMConvert(p->PCM,DstPlanes,SrcPlanes,&DstLength,&SrcLength,Speed,p->VolumeSoftLog);
if (p->VolumeRamp < (1<<RAMPSIZE))
{
uint8_t *u8,*u8e;
int16_t *s16,*s16e;
int Ramp = p->VolumeRamp;
int Inc = (10*(1<<RAMPSIZE)) / (p->OutputFormat.Channels * p->OutputFormat.SampleRate);
if (Inc<=0) Inc=1;
switch (p->OutputFormat.Bits)
{
case 8:
u8=DstPlanes[0];
u8e=u8+DstLength;
for (;u8!=u8e && Ramp<(1<<RAMPSIZE);++u8,Ramp+=Inc)
*u8 = (uint8_t)((((*u8 - 0x80) * Ramp) >> RAMPSIZE) + 0x80);
break;
case 16:
s16=DstPlanes[0];
s16e=s16+(DstLength>>1);
for (;s16!=s16e && Ramp<(1<<RAMPSIZE);++s16,Ramp+=Inc)
*s16 = (int16_t)((*s16 * Ramp) >> RAMPSIZE);
break;
default:
Ramp = RAMPSIZE;
break;
}
p->VolumeRamp = Ramp;
}
p->Bytes += DstLength;
p->FillPos += DstLength;
SrcPlanes[0] = (uint8_t*)SrcPlanes[0] + SrcLength;
SrcPlanes[1] = (uint8_t*)SrcPlanes[1] + SrcLength;
Length -= SrcLength;
if (!SrcLength)
break;
}
if (Length && p->InputFormat.BlockAlign>0)
p->Skip = p->InputFormat.BlockAlign - Length % p->InputFormat.BlockAlign;
Write(p,CurrTime);
return ERR_NONE;
}
static void CALLBACK WaveProc( HWAVEOUT hwo, UINT uMsg, DWORD dwInstance,
DWORD dwParam1, DWORD dwParam2 )
{
if (uMsg == WOM_DONE)
{
buffer* Buffer = (buffer*)(((WAVEHDR*)dwParam1)->dwUser);
waveout* p = (waveout*)dwInstance;
if (p->Pausing)
{
// add buffer to fill chain
Buffer->Next = *p->Pausing;
*p->Pausing = Buffer;
p->Pausing = &Buffer->Next;
}
else
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -