2

シンセサイザーでのオーディオ再生に使用している 4 つのバッファーがあります。最初に 2 つのバッファーを送信し、コールバック ルーチンで次のバッファーにデータを書き込み、そのバッファーを送信します。

各バッファーを生成するときは、周期がバッファー長の倍数である正弦波をそれに入れているだけです。

実行すると、各バッファ間で短い一時停止が聞こえます。バッファー サイズを 44100 Hz で 16K サンプルに増やしたので、バッファー全体が再生されていることがはっきりと聞こえますが、それぞれの間に中断があります。

私が考えているのは、コールバック関数は、書き込まれたすべてのバッファが完了したときにのみ呼び出されるということです。再生の前に合成を行う必要があるため、各バッファが完了したときにコールバックが必要です。

人々は通常、この問題をどのように解決しますか?

更新: コードを追加するように求められました。ここに私が持っているものがあります:

まず、WaveOut デバイスに接続します。

// Always grab the mapped wav device.
UINT deviceId = WAVE_MAPPER;

// This is an excelent tutorial:
// http://planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=4422&lngWId=3

WAVEFORMATEX wfx; 
wfx.nSamplesPerSec = 44100; 
wfx.wBitsPerSample = 16; 
wfx.nChannels = 1; 
wfx.cbSize = 0; 
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;

_waveChangeEventHandle = CreateMutex(NULL,false,NULL);

MMRESULT res;
res = waveOutOpen(&_wo, deviceId, &wfx, (DWORD_PTR)WavCallback, 
    (DWORD_PTR)this, CALLBACK_FUNCTION);

使用する 4 つのフレームを初期化します。

for (int i=0; i<_numFrames; ++i)
{
    WAVEHDR *header = _outputFrames+i;
    ZeroMemory(header, sizeof(WAVEHDR));
    // Block size is in bytes.  We have 2 bytes per sample.
        header->dwBufferLength = _codeSpec->OutputNumSamples*2; 
    header->lpData = (LPSTR)malloc(2 * _codeSpec->OutputNumSamples);
    ZeroMemory(header->lpData, 2*_codeSpec->OutputNumSamples);
    res = waveOutPrepareHeader(_wo, header, sizeof(WAVEHDR));
    if (res != MMSYSERR_NOERROR)
    {
        printf("Error preparing header: %d\n", res - MMSYSERR_BASE);
    }
}
SubmitBuffer();
SubmitBuffer();

SubmitBuffer コードは次のとおりです。

void Vodec::SubmitBuffer()
{
WAVEHDR *header = _outputFrames+_curFrame;
MMRESULT res;
res = waveOutWrite(_wo, header, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR)
{
    if (res = WAVERR_STILLPLAYING)
    {
        printf("Cannot write when still playing.");
    }
    else
    {
        printf("Error calling waveOutWrite: %d\n", res-WAVERR_BASE);
    }
}

_curFrame = (_curFrame+1)&0x3;

if (_pointQueue != NULL)
{
        RenderQueue();
    _nextFrame = (_nextFrame + 1) & 0x3;
}
}

そして、ここに私のコールバックコードがあります:

void CALLBACK Vodec::WavCallback(HWAVEOUT hWaveOut, 
    UINT uMsg, 
    DWORD dwInstance, 
    DWORD dwParam1,
    DWORD dwParam2 )
{
// Only listen for end of block messages.
if(uMsg != WOM_DONE) return;

    Vodec *instance = (Vodec *)dwInstance;
instance->SubmitBuffer();
}

RenderQueue コードは非常に単純です。テンプレート バッファーの一部を出力バッファーにコピーするだけです。

void Vodec::RenderQueue()
{
double white = _pointQueue->White;
white = 10.0; // For now just override with a constant value
int numSamples = _codeSpec->OutputNumSamples;
signed short int *data = (signed short int *)_outputFrames[_nextFrame].lpData;
for (int i=0; i<numSamples; ++i)
{
    Sample x = white * _noise->Samples[i];
    data[i] = (signed short int)(x);
}
_sampleOffset += numSamples;
if (_sampleOffset >= _pointQueue->DurationInSamples)
{
    _sampleOffset = 0;
    _pointQueue = _pointQueue->next;
}
}

更新: ほとんどの問題を解決しました。_curFrame と共に _nextFrame をインクリメントする必要があります (条件付きではありません)。再生バッファが書き込みバッファよりも先に進んでいました。

ただし、再生バッファを 1024 サンプルに減らすと、再び不安定になります。2048 サンプルでは明らかです。これは、デバッグ ビルドとリリース ビルドの両方で発生します。

4

1 に答える 1

0

1024 サンプルは、約 23 ミリ秒のオーディオ データに相当します。wav は、Windows Vista 以降のかなり高レベルの API です。低遅延のオーディオ再生が必要な場合は、CoreAudioを使用する必要があります。遅延は、共有モードで 10 ミリ秒、排他モードで 3 ミリ秒まで短縮できます。また、オーディオは、システムで現在実行されているプロセスに依存します。つまり、データを取得するためにオーディオ スレッドを実行できる頻度によって異なります。MultiMedia Class Scheduler ServiceAvSetMmThreadCharacteristics関数も参照してください。

于 2013-09-09T10:10:05.227 に答える