Направление звука с потоковым буфером - блокировка не переносится! Использование перенесенных заголовков DirectX для Delphi - PullRequest
3 голосов
/ 22 января 2010

Правильно, я работаю над внедрением DirectSound в наше приложение Delphi voip (приложение позволяет использовать радио для нескольких пользователей по сетевому соединению) Данные поступают через UDP-трансляции. Как и сейчас, мы снижаем уровень необработанных данных и сами микшируем аудио из нескольких источников и получаем централизованный компонент, который используется для воспроизведения всего этого.

Само приложение является приложением Delphi 5, и мне поручено портировать его на Delphi 2010. Как только я добрался до этой части воспроизведения аудио, мы пришли к выводу, что лучше всего, если мы сможем избавиться от этого старого кода и заменить его с направленным звуком.

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

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

Я использую перенесенные заголовки DirectX для Delphi (http://www.clootie.ru/delphi/download_dx92.html)

Смысл этих заголовков в том, чтобы портировать через обычный интерфейс DirectSound на Delphi, так что, надеюсь, не-Delphi программисты с DirectSound могут знать, в чем причина моей проблемы.

Мой SecondaryBuffer (IDirectSoundBuffer) был создан следующим образом:

var
  BufferDesc: DSBUFFERDESC;
  wfx: tWAVEFORMATEX;


wfx.wFormatTag := WAVE_FORMAT_PCM;
wfx.nChannels := 1;
wfx.nSamplesPerSec := 8000;
wfx.wBitsPerSample := 16;
wfx.nBlockAlign := 2; // Channels * (BitsPerSample/2)
wfx.nAvgBytesPerSec := 8000 * 2; // SamplesPerSec * BlockAlign

BufferDesc.dwSize := SizeOf(DSBUFFERDESC);
BufferDesc.dwFlags := (DSBCAPS_GLOBALFOCUS or DSBCAPS_GETCURRENTPOSITION2 or DSBCAPS_CTRLPOSITIONNOTIFY);
BufferDesc.dwBufferBytes := wfx.nAvgBytesPerSec * 4; //Which should land at 64000
BufferDesc.lpwfxFormat  := @wfx;


case DSInterface.CreateSoundBuffer(BufferDesc, DSCurrentBuffer, nil) of
  DS_OK: ;
  DSERR_BADFORMAT: ShowMessage('DSERR_BADFORMAT');
  DSERR_INVALIDPARAM: ShowMessage('DSERR_INVALIDPARAM');
  end;

Я пропустил части, в которых я определил мой PrimaryBuffer (он настроен для воспроизведения с флагом зацикливания и был создан именно так, как MSDN говорит, что это должно быть) и DSInterface, но это, как вы можете себе представить, IDirectSoundInterface.

Теперь, каждый раз, когда я получаю звуковое сообщение (обнаруженное, декодированное и преобразованное в соответствующий аудиоформат другими созданными нами компонентами, которые были подтверждены для работы более семи лет), я делаю следующее:

DSCurrentBuffer.Lock(0, 512, @FirstPart, @FirstLength, @SecondPart, @SecondLength, DSBLOCK_FROMWRITECURSOR);
Move(AudioData, FirstPart^, FirstLength);
if SecondLength > 0 then
  Move(AudioData[FirstLength], SecondPart^, SecondLength);

DSCurrentBuffer.GetStatus(Status);
DSCurrentBuffer.GetCurrentPosition(@PlayCursorPosition, @WriteCursorPosition);
if (FirstPart <> nil) or (SecondPart <> nil) then
  begin
    Memo1.Lines.Add('FirstLength = ' + IntToStr(FirstLength));
    Memo1.Lines.Add('PlayCursorPosition = ' + IntToStr(PlayCursorPosition));
    Memo1.Lines.Add('WriteCursorPosition = ' + IntToStr(WriteCursorPosition));
  end;
DSCurrentBuffer.Unlock(@FirstPart, FirstLength, @SecondPart, SecondLength);

AudioData содержит данные в моем сообщении. Сообщения всегда содержат 512 байт аудиоданных. Я добавил строки Memo1.Lines.Add, чтобы иметь возможность получить отладочный вывод (поскольку использование точек останова не совсем работает, так как directsound продолжает воспроизводить содержимое основного буфера независимо)

Теперь, когда я играю в свой DSCurrentBuffer, используя флаг цикла (который в соответствии с документами MSDN достаточно для того, чтобы сделать его потоковым буфером), и этот код работает так, как он хочет, мой выводимый текст в заметке показывает, что Мне разрешено писать до конца буфера ... Но он не переносится.

SecondPart всегда равен нулю. Он никогда не переносится в начало буфера, что означает, что я получаю одни и те же несколько секунд воспроизведения аудиоданных снова и снова.

И да, я искал сеть для компонентов, которые могут сделать это для нас, и пришел к выводу, что единственный надежный способ - это сделать это самим.

И да, аудиоданные, воспроизводимые этим приложением, нестабильны. Я задерживаюсь на написании полусекундного буферизирующего кода, пока не получу код записи в буфер для переноса, как следует: /

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

Любая помощь с благодарностью!

Ответы [ 2 ]

2 голосов
/ 22 января 2010

Так что я разобрался с проблемой ^^;

Довольно просто тоже.

DSCurrentBuffer.Unlock(@FirstPart, FirstLength, @SecondPart, SecondLength);

Я думал, что должен был просто передать те же указатели указателям, которые требовал Lock ().

Изменение на

DSCurrentBuffer.Unlock(FirstPart, FirstLength, SecondPart, SecondLength);

Решена проблема, и буфер теперь правильно переносится.

Извините, что потратил ваше время, но все равно спасибо ^^;

1 голос
/ 22 января 2010

Несколько вещей, которые могут вызвать это:

  1. Memo1.Lines.Add следует вызывать только из основного потока (потока, который инициализировал графический интерфейс VCL). Используйте TThread.Synchronize для этого (проще) или промежуточный буфер, который является потокобезопасным и предпочтительно без блокировки (быстрее; спасибо mghie за этот совет) .

  2. Разблокировка должна быть в разделе finally , как показано ниже, поскольку, если возникает исключение, вы никогда не разблокируете буфер, см. Пример кода ниже.

  3. Вы должны регистрировать любые возникающие исключения.

Пример кода:

  DSCurrentBuffer.Lock(0, 512, @FirstPart, @FirstLength, @SecondPart, @SecondLength, DSBLOCK_FROMWRITECURSOR);
  try
    //...
  finally
    DSCurrentBuffer.Unlock(@FirstPart, FirstLength, @SecondPart, SecondLength);
  end;

- Йерун

...