Directsound - Проблемы с воспроизведением потоковой передачи буфера, заполненного данными из сети! Используя портированные заголовки DirectX для Delphi

Назад снова с еще одним вопросом о 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 является процедурой, определенной в моем приложении. Поток запускает criticalsection и называет его, когда уведомление инициировано. (WaitForSingleObject называют с тайм-аутом 20 мс так, чтобы я мог обновить дескриптор каждый раз, когда CreateEvent называют, конечно.)

ReachedLastWrittenByte () делает следующее:

DSCurrentBuffer.Stop;
LastWrittenByte := 0;

Когда мое уведомление инициировано, и я называю Остановку на Вторичном Буфере, который я использую, аудио все еще сохраняет цикличное выполнение, что, кажется, оставшиеся данные в основном буфере...

И даже затем, уведомления правильно не инициированы. Если я прекращаю передачу аудиоданных из другого приложения, которое отправляет эти аудио сообщения, это просто сохраняет цикличное выполнение по остаткам в буфере. Так в основном это идет мимо последнего уведомления, которое я установил (lastwrittenbyte) независимо. Когда в воспроизведении, это просто иногда заделывает, заполняет буфер и затем начинает играть... И пропуская наполовину второй из данных это просто буферизовало, просто идя вперед для проигрывания данных, которые входят после того, как буфер заполнен (таким образом, это заполняет буфер, но по-видимому не хочет играть свое содержание, прежде чем это начнет заполнять новые данные там. Да. Я понятия не имею также.)

Что я, кажется, пропускаю здесь? Идея состоит в том, чтобы использовать DirectSound Notificatiosn для обнаружения, когда последний раз записанный байт играется бесплодное усилие? Вы думали бы, что будет НЕКОТОРЫЙ способ сделать этот вид буферизации с буфером потоковой передачи.

6
задан Michael Stahre 27 January 2010 в 16:47
поделиться

1 ответ

И снова я обнаружил, что отвечаю на свой вопрос ^^;

Возникли три проблемы: Во-первых, я смотрел на Позиция курсора воспроизведения, чтобы определить, достиг ли воспроизведение последнего записанного байта. Это работает не совсем правильно, потому что, хотя курсор воспроизведения показывает, что в настоящее время воспроизводится, он не сообщает вам, какой последний байт набора данных для воспроизведения. Курсор записи всегда расположен немного впереди курсора воспроизведения. Область между курсором воспроизведения и курсором записи заблокирована для воспроизведения.

Другими словами, я смотрел не на тот курсор.

Во-вторых, и вот основная причина, по которой все работает не так, как должно, заключается в том, что я записывал данные с флагом DSBLOCK_FROMWRITECURSOR. Это позволяет мне записывать данные из курсора записи и далее . Каждый раз.

Это означает, что когда я думал, что это буферизация данных, на самом деле он просто записывал одни и те же данные снова и снова. Курсор записи, вопреки тому, что я предполагал, не перемещается вперед, когда вы пишете данные в буфер. Теперь я отслеживаю последний записанный байт вручную и просто выполняю DSBLOCK_ENTIREBUFFER для этого смещения. Я обрабатываю перенос, просто отслеживая размер буфера и мои lastwrittenbyte var и Подтвердите, что это не переносится. Помогает то, что мне всегда нужно записывать в буфер ровно 512 байт данных. Делая размер моего буфера кратным 512, мне нужно только убедиться, что если lastwrittenbyte> = buffersize, то lastwrittenbyte: = 0 (я изменил lastwrittenbyte на следующий байт, так как теперь он скорее показывает следующий байт для записи. Таким образом я не нужно беспокоиться о неравномерности байтов, выровняйте их с помощью + 1 и тому подобное.)

Итак, предполагаемый метод потоковой передачи данных в буфер заставляет вас снова и снова записывать свои собственные данные. {{1 }} Как они намеревались правильно использовать это для потоковой передачи данных в буфер ... Я действительно не знаю. Возможно, они думают, что вы можете поместить уведомление посередине и рассматривать его как разделенный буфер?

Это подводит меня к третьей проблеме: Я пытался использовать уведомления. Оказывается, уведомления ненадежны, и люди в целом рекомендуют вам избегать их использования, а если вам нужно, то вам не следует использовать больше нескольких.

Поэтому я отказался от уведомлений и добавил таймер с высоким разрешением, который срабатывает каждые 30 мс (пакеты данных, которые я получаю и извлекающие аудио для воспроизведения по желанию, согласно нашему протоколу, всегда отправляются с интервалом 32 мс. , так что все, что меньше 32 мс, действительно работает для этого таймера.)

Я отслеживаю, сколько данных у меня осталось для воспроизведения, с помощью простой переменной BytesInBuffer. Когда срабатывает мой таймер, я проверяю, насколько продвинулся курсор записи (который, как я сказал ранее, по сути, является истинным курсором воспроизведения) с момента последнего срабатывания таймера, и удаляю это число из моей переменной BytesInBuffer.

Оттуда я могу либо посмотреть, не закончились ли у меня байты, либо я могу посмотреть на позицию курсора записи и проверить, не превысил ли он мой последний записанный байт (помогает знание того, сколько байтов я сыграл с момента моего последнего события таймера. .

if (BytesInBuffer <= 0) or ((WritePos >= LastWrittenByte) and (WritePos-PosDiff <= LastWrittenByte)) then
  DSBuffer.Stop;

Итак, вот.

Всегда отстойно находить людей, задающих тот же вопрос, что и вы, только для того, чтобы в конце концов сказать: «Неважно, я решил это» и не оставить ответа.

Надеюсь, это может просветить кого-то еще будущее.

10
ответ дан 10 December 2019 в 00:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: