Вернемся к еще одному вопросу о DirectSound, который касается способов использования буферов DirectSound:
У меня есть пакеты, поступающие по сети с интервалами приблизительно 30 мс, содержащие аудиоданные, которые декодируются в необработанные wav-данные другими частями приложения.
Когда событие Indata запускается этими другими частями кода, я, по сути, попадаю в процедуру с аудиоданными в качестве параметра.
DSCurrentBuffer был инициализирован следующим образом:
ZeroMemory(@BufferDesc, SizeOf(DSBUFFERDESC));
wfx.wFormatTag := WAVE_FORMAT_PCM;
wfx.nChannels := 1;
wfx.nSamplesPerSec := fFrequency;
wfx.wBitsPerSample := 16;
wfx.nBlockAlign := 2; // Channels * (BitsPerSample/8)
wfx.nAvgBytesPerSec := fFrequency * 2; // SamplesPerSec * BlockAlign
BufferDesc.dwSize := SizeOf(DSBUFFERDESC);
BufferDesc.dwFlags := (DSBCAPS_GLOBALFOCUS or DSBCAPS_GETCURRENTPOSITION2 or
DSBCAPS_CTRLPOSITIONNOTIFY);
BufferDesc.dwBufferBytes := BufferSize;
BufferDesc.lpwfxFormat := @wfx;
case DSInterface.CreateSoundBuffer(BufferDesc, DSCurrentBuffer, nil) of
DS_OK:
;
DSERR_BADFORMAT:
ShowMessage('DSERR_BADFORMAT');
DSERR_INVALIDPARAM:
ShowMessage('DSERR_INVALIDPARAM');
end;
Я записываю эти данные в мой вторичный буфер следующим образом:
var
FirstPart, SecondPart: Pointer;
FirstLength, SecondLength: DWORD;
AudioData: Array [0 .. 511] of Byte;
I, K: Integer;
Status: Cardinal;
begin
Входные данные здесь преобразуются в аудиоданные, не относящиеся к самому вопросу.
DSCurrentBuffer.GetStatus(Status);
if (Status and DSBSTATUS_PLAYING) = DSBSTATUS_PLAYING then // If it is playing, request the next segment of the buffer for writing
begin
DSCurrentBuffer.Lock(LastWrittenByte, 512, @FirstPart, @FirstLength,
@SecondPart, @SecondLength, DSBLOCK_FROMWRITECURSOR);
move(AudioData, FirstPart^, FirstLength);
LastWrittenByte := LastWrittenByte + FirstLength;
if SecondLength > 0 then
begin
move(AudioData[FirstLength], SecondPart^, SecondLength);
LastWrittenByte := SecondLength;
end;
DSCurrentBuffer.GetCurrentPosition(@PlayCursorPosition,
@WriteCursorPosition);
DSCurrentBuffer.Unlock(FirstPart, FirstLength, SecondPart, SecondLength);
end
else // If it isn't playing, set play cursor position to the start of buffer and lock the entire buffer
begin
if LastWrittenByte = 0 then
DSCurrentBuffer.SetCurrentPosition(0);
LockResult := DSCurrentBuffer.Lock(LastWrittenByte, 512, @FirstPart, @FirstLength,
@SecondPart, @SecondLength, DSBLOCK_ENTIREBUFFER);
move(AudioData, FirstPart^, 512);
LastWrittenByte := LastWrittenByte + 512;
DSCurrentBuffer.Unlock(FirstPart, 512, SecondPart, 0);
end;
Выше приведен код, который я запускаю в своем событии OnAudioData (определенном нашим проприетарным компонентом, который декодирует сообщения, отправленные по нашему протоколу).
По сути, код запускается всякий раз, когда я получаю сообщение UDP с аудиоданными.
После записи в буфер я делаю следующее, чтобы начать воспроизведение, как только в буфере окажется достаточно данных:
Кстати, для BufferSize в данный момент установлено значение, эквивалентное 1 секунде.
if ((Status and DSBSTATUS_PLAYING) <> DSBSTATUS_PLAYING) and
(LastWrittenByte >= BufferSize div 2) then
DSCurrentBuffer.Play(0, 0, DSCBSTART_LOOPING);
Пока все хорошо, хотя воспроизведение звука немного прерывистое.
К сожалению, этого кода недостаточно.
Мне также нужно остановить воспроизведение и подождать, пока буфер снова заполнится, когда в нем закончатся данные.
Здесь я сталкиваюсь с проблемами.
По сути, мне нужно иметь возможность узнать, когда воспроизведение аудио достигло точки, когда я в последний раз записывал в буфер, и остановить его, когда это произойдет. В противном случае, конечно, любые задержки в аудиоданных, которые я получаю, могут испортить звук.
К сожалению, я не могу просто прекратить запись в буфер, потому что если я позволю буферу продолжить воспроизведение, он просто воспроизведет старые данные, оставленные там одну секунду назад (что с циклическим и все.)
Поэтому мне нужно знать, достигло ли PlayCursorPosition значения LastWrittenByte, которое я отслеживаю в своем коде для записи в буфер.
Уведомления DirectSound, казалось, могли сделать это для меня, но остановка и последующий запуск буфера заново каждый раз, когда я записываю в него данные (SetNotificationPositions () требует остановки буфера), оказывает заметное влияние на само воспроизведение, поэтому звук, который воспроизводится, звучит еще более прерывисто, чем раньше.
Я добавил это в конец своего кода для получения уведомлений. Я ожидал, что установлю новое Уведомление каждый раз, когда я записываю данные в буфер, вероятно, не будет работать слишком хорошо ... Но, эй, я подумал, что попытка не помешает:
if (Status and DSBStatus_PLAYING) = DSBSTATUS_PLAYING then //If it's playing, create a notification
begin
DSCurrentBuffer.QueryInterface(IID_IDirectSoundNotify, DSNotify);
NotifyDesc.dwOffset := LastWrittenByte;
NotifyDesc.hEventNotify := NotificationThread.CreateEvent(ReachedLastWrittenByte);
DSCurrentBuffer.Stop;
LockResult := DSNotify.SetNotificationPositions(1, @NotifyDesc);
DSCurrentBuffer.Play(0, 0, DSBPLAY_LOOPING);
end;
NotificationThread - это поток, который выполняет WaitForSingleObject. CreateEvent создает новый дескриптор события и делает его так, чтобы WaitForSingleObject ожидал этого вместо предыдущего.
ReachedLastWrittenByte - это процедура, определенная в моем приложении. Поток запускает критический раздел и вызывает его при срабатывании уведомления.
(WaitForSingleObject вызывается с тайм-аутом 20 мс, так что я могу обновлять дескриптор всякий раз, когда вызывается CreateEvent, конечно.)
ReachedLastWrittenByte () выполняет следующие действия:
DSCurrentBuffer.Stop;
LastWrittenByte := 0;
Когда мое уведомление срабатывает, и я вызываю Стоп для используемого мной вторичного буфера, звук все еще продолжает зацикливаться на том, что кажется оставшимися данными в первичном буфере ...
И даже тогда уведомления не запускаются должным образом. Если я прекращаю трансляцию аудиоданных из другого приложения, отправляющего эти аудиосообщения, оно просто продолжает зацикливаться на остатках в буфере.
Так что, в основном, он проходит мимо последнего установленного мной уведомления (lastwrittenbyte) независимо.При воспроизведении он просто время от времени останавливается, заполняет буфер и затем начинает воспроизведение ... И пропуская полсекунды данных, которые он только что буферизировал, просто продолжая воспроизводить данные, поступающие после заполнения буфера заполняет буфер, но, видимо, не хочет воспроизводить его содержимое до того, как он начнет заполнять новые данные. Да. Я тоже понятия не имею.)
Чего мне здесь не хватает? Является ли идея использовать DirectSound Notificatiosn, чтобы выяснить, когда последний записанный байт исполняется бесполезно? Вы могли бы подумать, что есть НЕКОТОРЫЙ способ сделать такую буферизацию с помощью потокового буфера.