2010-01-27 2 views
4

DirectSound 버퍼를 사용할 수있는 방법에 관한 또 다른 DirectSound 질문으로 다시 돌아가십시오.DirectSound - 네트워크의 데이터로 가득 찬 스트리밍 버퍼 재생 문제!

디코딩 된 오디오 데이터가 들어있는 약 30ms 간격으로 네트워크를 통해 들어오는 패킷이 있습니다. 응용 프로그램의 다른 부분에서 원시 wav 데이터.

Indata 이벤트가 이러한 다른 코드에 의해 트리거되면 필자는 오디오 데이터가있는 프로 시저를 매개 변수로 사용합니다.

var 
FirstPart, SecondPart: Pointer; 
FirstLength, SecondLength: DWORD; 
AudioData: Array [0 .. 511] of Byte; 
I, K: Integer; 
Status: Cardinal; 
begin 

입력 데이터는 질문 자체와 관련이없는, 여기에 오디오 데이터로 변환되어 다음과 같이

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; 

내가 내 차 버퍼에 데이터를 쓰기 다음과 같이

DSCurrentBuffer는 초기화 .

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); 

오디오 재생이 약간 고르지 만 지금까지는 그렇게 좋았습니다. 아쉽게도이 코드로는 충분하지 않습니다.

또한 재생을 멈추고 데이터가 부족한 경우 버퍼가 다시 채워질 때까지 기다려야합니다. 여기가 내가 문제를 겪고있는 곳입니다.

기본적으로 오디오 재생이 내가 마지막으로 버퍼에 쓴 지점에 도달했을 때이를 알아낼 수 있어야합니다. 그렇지 않으면 내가받는 오디오 데이터의 지연으로 인해 오디오가 엉망이 될 것입니다. 불행히도 버퍼에 계속 쓰는 것을 멈출 수는 없습니다. 버퍼를 계속 재생하면 1 초 전의 이전 데이터가 재생됩니다 (원형과 모두가 함께있는 것입니다). 그래서 if PlayCursorPosition이 버퍼 작성 코드에서 추적하는 LastWrittenByte 값에 도달했습니다.

DirectSound Notifications는 나를 위해 할 수있는 것처럼 보였지만 데이터를 쓸 때마다 (즉, 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는 내 응용 프로그램에 정의 된 프로 시저입니다. 스레드는 임계 섹션을 시작하고 알림이 트리거 될 때이를 호출합니다. 가)

ReachedLastWrittenByte ((.의 WaitForSingleObject는 내가 CreateEvent에은 물론, 호출 될 때마다 핸들을 업데이트 할 수 있도록이 20ms의 타임 아웃이라고합니다)은 다음을

내 통지가 트리거
DSCurrentBuffer.Stop; 
LastWrittenByte := 0; 

및 내가 사용하는 보조 버퍼에서 Stop을 호출하면 오디오는 여전히 기본 버퍼의 나머지 데이터 인 것처럼 보이는 루프를 반복합니다 ...

그리고 알림이 제대로 트리거되지 않습니다. 이러한 오디오 메시지를 보내는 다른 응용 프로그램에서 오디오 데이터의 브로드 캐스트를 중지하면 버퍼의 남은 부분을 반복적으로 반복합니다. 기본적으로, 내가 설정 한 최신 알림 (lastwrittenbyte)을 무시하고지나갑니다. 재생 중에 그냥 가끔 멈추고 버퍼를 채운 다음 재생을 시작합니다 ... 단지 버퍼 된 데이터의 절반을 건너 뛰고 버퍼가 채워진 후에 들어오는 데이터를 재생하려고합니다 (그래서 버퍼를 채우지 만 새 데이터가 채워지기 전에 내용을 재생하는 데 신경 쓰지 않는 것 같습니다. 예, 잘 모르겠습니다.)

여기에 누락 된 부분이 있습니까? DirectSound Notificatiosn을 사용하여 가장 최근에 기록 된 바이트가 효과가없는 노력을 한시기를 알아내는 아이디어입니까? 스트리밍 버퍼로 이런 종류의 버퍼링을하는 방법이 있다고 생각할 것입니다.

답변

8

그리고 나 자신도 내 자신의 질문에 대답했다. ^^;

세 가지 문제가있었습니다. 먼저 재생 커서 위치에서 재생이 마지막으로 기록 된 바이트에 도달했는지 확인했습니다. 재생 커서가 현재 재생중인 것을 표시하기 때문에 재생할 데이터 세트의 마지막 바이트가 무엇인지 알려주지 않으므로 제대로 작동하지 않습니다. 쓰기 커서가 인 경우 항상이 재생 커서보다 약간 앞에 위치합니다. 재생 커서와 쓰기 커서 사이의 영역이 재생을 위해 잠겨 있습니다.

즉, 잘못된 커서를보고있었습니다.

둘째, 여기에 DSBLOCK_FROMWRITECURSOR 플래그로 데이터를 쓰고 있다는 점에서 중요한 점이 있습니다. 이 기능을 사용하면 커서 쓰기 및 이후부터 데이터를 쓸 수 있습니다. 때마다.

즉 데이터를 버퍼링한다고 생각하면 실제로 동일한 데이터를 반복해서 쓰는 것입니다. 쓰기 커서는 데이터를 버퍼에 쓸 때 앞으로 나아 가지 않습니다. 그래서 지금 내가하는 일은 내가 마지막으로 작성한 바이트를 수동으로 추적하고 해당 오프셋에서 DSBLOCK_ENTIREBUFFER를 수행한다는 것입니다. 버퍼 크기와 lastwrittenbyte var를 추적하여 랩핑을 처리하고 랩핑하지 않는지 확인합니다. 그것은 단지 버퍼에 정확히 512 바이트의 데이터를 언제든지 쓸 필요가 있습니다. 내 버퍼 크기를 512의 배수로 만드는 것은 lastwrittenbyte> = buffersize 인 경우 lastwrittenbyte : = 0 (lastbytenbyte를 nextbyte로 변경했는지 확인해야한다는 것을 의미합니다. 고르지 않은 바이트에 대해 걱정할 필요가 없습니다. + 1 및 모든 것을 저녁으로 내 보내세요.)

버퍼에 데이터를 스트리밍하는 방법으로 데이터를 다시 작성해야합니다. 어떻게 데이터를 버퍼로 스트리밍하기 위해 적절히 사용하려고했는지 ... 나는 정말로 모른다. 아마도 그들은 중간에 알림을 넣고 분할 된 버퍼처럼 취급 할 수 있다고 생각하고 있습니까?

다음 세 번째 문제가 발생합니다. 알림을 사용하려고했습니다. 알림은 신뢰할 수 없으므로 사람들은 전반적으로 알림을 사용하지 않는 것이 좋습니다. 그렇다면 꼭 사용해야하는 경우가 많습니다.

그래서 알림을 없앴으며 매 30ms마다 트리거하는 고해상도 타이머를 버렸습니다 (받은 데이터 패킷과 우리 프로토콜에 따라 재생할 오디오를 추출하는 것은 항상 32ms 간격, 그래서 32ms 미만이 타이머 정말 작동합니다.)

나는 간단한 BytesInBuffer 변수로 재생할 남은 데이터의 양을 추적합니다. 내 타이머가 트리거되면 필자는 마지막으로 타이머가 트리거 된 이후로 쓰기 커서 (본질적으로 실제 재생 커서라고 말한 것)가 얼마나 향상되었는지 확인하고,이 숫자를 내 BytesInBuffer var에서 제거합니다.

거기에서 나는 바이트가 부족한 지 살펴볼 수 있습니다. 또는 writecursor 위치를보고 lastwrittenbyte를 초과하는지 확인할 수 있습니다 (마지막 타이머 이벤트 이후에 얼마나 많은 바이트를 가지고 있는지 알고 있음). 여기에. 그것은 항상 당신은 결국 "신경 끄시 고 나는 그것을 해결"가서 답변을 남겨하지과 같은 질문을 사람을 찾는 안됐다. 그래서이

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

.

을하는 데 도움이 기대에의 이것은 th 안에 다른 누군가를 계몽 할지도 모르다 미래.

+0

'항상 같은 질문을하는 사람들을 찾으면 결국에는 "해결할 생각이 없다"고 대답하지 않고 떠나야합니다. "<---- +1. – Jace

관련 문제