forked from LosaDev/ls-2019-source
505 lines
12 KiB
C++
505 lines
12 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// BandiVideoLibrary 2.1
|
|
///
|
|
/// Copyright(C) 2008-2011 BandiSoft.com All rights reserved.
|
|
///
|
|
/// Visit http://www.bandisoft.com for more information.
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <windows.h>
|
|
#include <process.h>
|
|
#include <math.h>
|
|
#include "BandiVideoSound_DS.h"
|
|
|
|
#define BVL_DSWND_CLASS_NAME _T("CBandiVideoSound_DS")
|
|
|
|
|
|
CBandiVideoSound_DS::CBandiVideoSound_DS() : m_ref_count(0)
|
|
{
|
|
m_hwnd = NULL;
|
|
m_hinst = NULL;
|
|
m_ds = NULL;
|
|
m_dsb_primary = NULL;
|
|
m_dsb_secondary = NULL;
|
|
|
|
m_channels = m_samplerate = 0;
|
|
m_last_write_pos = 0;
|
|
m_inbuf_samples = m_total_samples = 0;
|
|
|
|
m_cur_play_time = 0;
|
|
|
|
memset(&m_crit_sec, NULL, sizeof(m_crit_sec));
|
|
m_crit_sec_inited = FALSE;
|
|
|
|
m_hevent = NULL;
|
|
m_hthread = NULL;
|
|
m_thread_id = 0;
|
|
|
|
m_playing = FALSE;
|
|
m_callback = NULL;
|
|
|
|
m_outbuf_size_max = -1;
|
|
m_outbuf_size_min = -1;
|
|
|
|
memset(m_sample_block, NULL, PLAY_SAMPLE_BLOCK_LEN);
|
|
m_sample_block_size = 0;
|
|
}
|
|
|
|
|
|
CBandiVideoSound_DS::~CBandiVideoSound_DS()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
|
|
STDMETHODIMP_(ULONG) CBandiVideoSound_DS::AddRef()
|
|
{
|
|
LONG lRef = InterlockedIncrement(&m_ref_count);
|
|
ASSERT(lRef >= 0);
|
|
|
|
return m_ref_count;
|
|
}
|
|
|
|
|
|
STDMETHODIMP_(ULONG) CBandiVideoSound_DS::Release()
|
|
{
|
|
LONG lRef = InterlockedDecrement(&m_ref_count);
|
|
ASSERT(lRef >= 0);
|
|
|
|
if(lRef == 0)
|
|
{
|
|
delete this;
|
|
return ULONG(0);
|
|
}
|
|
|
|
return m_ref_count;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Open sound device.
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CBandiVideoSound_DS::Open(INT channels, INT samplerate, IBandiVideoSoundCB *callback)
|
|
{
|
|
Close();
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
m_hinst = (HINSTANCE)GetModuleHandle(NULL);
|
|
if(m_hinst == NULL) goto error;
|
|
|
|
WNDCLASS wc;
|
|
ZeroMemory(&wc, sizeof(wc));
|
|
wc.lpfnWndProc = DefWindowProc;
|
|
wc.lpszClassName = BVL_DSWND_CLASS_NAME;
|
|
RegisterClass(&wc);
|
|
|
|
// 버퍼타임으로 버퍼 byte 크기 계산
|
|
m_outbuf_size_max = MulDiv(samplerate * channels * 2, MAX_BUFFER_TIME , 1000) ;
|
|
m_outbuf_size_min = MulDiv(samplerate * channels * 2, MIN_BUFFER_TIME ,1000 );
|
|
|
|
// Create dummy window
|
|
m_hwnd = CreateWindow(BVL_DSWND_CLASS_NAME, _T(""), 0, 0, 0, 0, 0, NULL, NULL, m_hinst, 0L);
|
|
if(m_hwnd == NULL)
|
|
goto error;
|
|
|
|
// Create Direct Sound Instance
|
|
hr = DirectSoundCreate8(NULL, &m_ds, NULL);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_ds->SetCooperativeLevel(m_hwnd, DSSCL_PRIORITY);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
// Create a primary buffer
|
|
DSBUFFERDESC dsbdesc;
|
|
ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC));
|
|
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
|
|
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
|
hr = m_ds->CreateSoundBuffer(&dsbdesc, &m_dsb_primary, NULL);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
// Set format of the primary buffer
|
|
WAVEFORMATEX wfx;
|
|
ZeroMemory(&wfx, sizeof(WAVEFORMATEX));
|
|
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.nChannels = channels;
|
|
wfx.nSamplesPerSec = samplerate;
|
|
wfx.wBitsPerSample = 16;
|
|
wfx.nBlockAlign = wfx.wBitsPerSample / 8 * wfx.nChannels;
|
|
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
|
hr = m_dsb_primary->SetFormat(&wfx);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
// Create secondary sound buffer
|
|
ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC));
|
|
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
|
|
dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS
|
|
/*| DSBCAPS_LOCDEFER | DSBCAPS_CTRLFREQUENCY*/ | DSBCAPS_CTRLVOLUME;
|
|
dsbdesc.dwBufferBytes = m_outbuf_size_max;
|
|
dsbdesc.lpwfxFormat = &wfx;
|
|
hr = m_ds->CreateSoundBuffer(&dsbdesc, &m_dsb_secondary, NULL);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
InitializeCriticalSection(&m_crit_sec);
|
|
m_crit_sec_inited = TRUE;
|
|
|
|
// Create an event object for periodical processing
|
|
m_hevent = CreateEvent(NULL, FALSE, FALSE, NULL); // auto reset event
|
|
if(m_hevent == NULL)
|
|
goto error;
|
|
|
|
// Create thread for playback
|
|
m_hthread = (HANDLE)_beginthreadex(NULL, 0, InitialThreadProc, this, CREATE_SUSPENDED, &m_thread_id);
|
|
if(m_hthread == NULL)
|
|
goto error;
|
|
|
|
m_channels = channels;
|
|
m_samplerate = samplerate;
|
|
|
|
m_callback = callback;
|
|
|
|
memset(m_sample_block, NULL, PLAY_SAMPLE_BLOCK_LEN);
|
|
m_sample_block_size = 0;
|
|
|
|
SetThreadPriority(m_hthread, THREAD_PRIORITY_TIME_CRITICAL);
|
|
ResumeThread(m_hthread);
|
|
|
|
return S_OK;
|
|
|
|
error:
|
|
Close();
|
|
|
|
return FAILED(hr) ? hr : E_FAIL;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Close sound device.
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CBandiVideoSound_DS::Close()
|
|
{
|
|
HANDLE hThread = (HANDLE)InterlockedExchangePointer(&m_hthread, 0);
|
|
if(hThread)
|
|
{
|
|
if(m_hevent) SetEvent(m_hevent);
|
|
|
|
WaitForSingleObject(hThread, INFINITE);
|
|
CloseHandle(hThread);
|
|
}
|
|
|
|
if(m_hevent)
|
|
{
|
|
CloseHandle(m_hevent);
|
|
m_hevent = NULL;
|
|
}
|
|
|
|
if(m_crit_sec_inited)
|
|
{
|
|
DeleteCriticalSection(&m_crit_sec);
|
|
m_crit_sec_inited = FALSE;
|
|
}
|
|
|
|
if(m_dsb_secondary)
|
|
{
|
|
m_dsb_secondary->Release();
|
|
m_dsb_secondary = NULL;
|
|
}
|
|
|
|
if(m_dsb_primary)
|
|
{
|
|
m_dsb_primary->Release();
|
|
m_dsb_primary = NULL;
|
|
}
|
|
|
|
if(m_ds)
|
|
{
|
|
m_ds->Release();
|
|
m_ds = NULL;
|
|
}
|
|
|
|
if(m_hwnd)
|
|
{
|
|
DestroyWindow(m_hwnd);
|
|
UnregisterClass(BVL_DSWND_CLASS_NAME, m_hinst);
|
|
m_hwnd = NULL;
|
|
}
|
|
|
|
m_channels = m_samplerate = 0;
|
|
m_last_write_pos = 0;
|
|
m_inbuf_samples = m_total_samples = 0;
|
|
m_cur_play_time = 0;
|
|
|
|
m_playing = FALSE;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Start
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CBandiVideoSound_DS::Start()
|
|
{
|
|
EnterCriticalSection(&m_crit_sec);
|
|
|
|
m_playing = TRUE;
|
|
|
|
LeaveCriticalSection(&m_crit_sec);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Stop
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CBandiVideoSound_DS::Stop()
|
|
{
|
|
EnterCriticalSection(&m_crit_sec);
|
|
|
|
m_playing = FALSE;
|
|
|
|
if(m_dsb_secondary)
|
|
{
|
|
BYTE *pbData = NULL;
|
|
BYTE *pbData2 = NULL;
|
|
DWORD dwLength=0;
|
|
DWORD dwLength2=0;
|
|
|
|
m_dsb_secondary->Stop();
|
|
m_dsb_secondary->SetCurrentPosition(0);
|
|
|
|
// Lock DirectSound buffer
|
|
m_dsb_secondary->Lock(0, m_outbuf_size_max, (void**)&pbData, &dwLength, (void**)&pbData2, &dwLength2, 0);
|
|
|
|
if(pbData) memset(pbData, 0, dwLength);
|
|
if(pbData2) memset(pbData2, 0, dwLength2);
|
|
|
|
// Unlock the buffer
|
|
m_dsb_secondary->Unlock(pbData, dwLength, pbData2, dwLength2);
|
|
|
|
m_last_write_pos = 0;
|
|
m_total_samples = m_inbuf_samples = 0;
|
|
m_cur_play_time = 0;
|
|
|
|
memset(m_sample_block, NULL, PLAY_SAMPLE_BLOCK_LEN);
|
|
m_sample_block_size = 0;
|
|
}
|
|
|
|
LeaveCriticalSection(&m_crit_sec);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Stop
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CBandiVideoSound_DS::Pause(BOOL pause)
|
|
{
|
|
EnterCriticalSection(&m_crit_sec);
|
|
|
|
if(pause)
|
|
{
|
|
m_playing = FALSE;
|
|
if(m_dsb_secondary) m_dsb_secondary->Stop();
|
|
}
|
|
else
|
|
{
|
|
m_playing = TRUE;
|
|
}
|
|
|
|
LeaveCriticalSection(&m_crit_sec);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Get play time (100nsec units)
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CBandiVideoSound_DS::GetTime(INT64 &time)
|
|
{
|
|
EnterCriticalSection(&m_crit_sec);
|
|
|
|
time = m_cur_play_time;
|
|
|
|
|
|
LeaveCriticalSection(&m_crit_sec);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Set sound volume
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CBandiVideoSound_DS::SetVolume(INT volume)
|
|
{
|
|
if(m_dsb_secondary == NULL)
|
|
return E_POINTER;
|
|
|
|
double lfPower10 = pow(10.0,-10.0); // 10^(-10);
|
|
double lfVolume = (1 - lfPower10)*((double)volume/255) + lfPower10;
|
|
|
|
lfVolume = 10*log10(lfVolume);
|
|
lfVolume *= 300;
|
|
|
|
if(lfVolume < -10000)
|
|
lfVolume = -10000;
|
|
|
|
return m_dsb_secondary->SetVolume((LONG)lfVolume);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// ThreadProc
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UINT CBandiVideoSound_DS::ThreadProc()
|
|
{
|
|
BOOL bWorking = FALSE;
|
|
while(m_hthread)
|
|
{
|
|
EnterCriticalSection(&m_crit_sec);
|
|
{
|
|
if(m_playing)
|
|
PlayDS();
|
|
}
|
|
LeaveCriticalSection(&m_crit_sec);
|
|
|
|
WaitForSingleObject(m_hevent, 2);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Play DirectSound
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CBandiVideoSound_DS::PlayDS()
|
|
{
|
|
HRESULT hr;
|
|
int sample_bytes = m_channels*sizeof(INT16);
|
|
DWORD free_buffer_size = GetFreeBufferSize();
|
|
|
|
// Update play position
|
|
m_inbuf_samples = (m_outbuf_size_max - free_buffer_size)/(m_channels*sizeof(INT16));
|
|
m_cur_play_time = max((m_total_samples - m_inbuf_samples)*10000000i64/m_samplerate, 0);
|
|
|
|
// Transfer PCM data to Direct Sound Buffer
|
|
if(m_callback)
|
|
{
|
|
void *lock1_ptr = NULL;
|
|
void *lock2_ptr = NULL;
|
|
DWORD lock1_bytes = 0;
|
|
DWORD lock2_bytes = 0;
|
|
|
|
if(m_sample_block_size < PLAY_SAMPLE_BLOCK_LEN)
|
|
{
|
|
int req_samples = (PLAY_SAMPLE_BLOCK_LEN-m_sample_block_size)/sample_bytes;
|
|
int get_samples = m_callback->GetPcmData((INT16*)(&m_sample_block[m_sample_block_size]), req_samples);
|
|
m_sample_block_size += get_samples*sample_bytes;
|
|
}
|
|
|
|
if(m_sample_block_size == PLAY_SAMPLE_BLOCK_LEN && m_sample_block_size <= free_buffer_size)
|
|
{
|
|
hr = m_dsb_secondary->Lock(m_last_write_pos, m_sample_block_size,
|
|
&lock1_ptr, &lock1_bytes,
|
|
&lock2_ptr, &lock2_bytes, 0);
|
|
|
|
if(hr == DSERR_BUFFERLOST)
|
|
{
|
|
m_dsb_secondary->Restore();
|
|
hr = m_dsb_secondary->Lock(m_last_write_pos, m_sample_block_size,
|
|
&lock1_ptr, &lock1_bytes,
|
|
&lock2_ptr, &lock2_bytes, 0);
|
|
}
|
|
|
|
if(FAILED(hr)) return hr;
|
|
|
|
if(lock1_ptr) memcpy(lock1_ptr, m_sample_block, lock1_bytes);
|
|
if(lock2_ptr) memcpy(lock2_ptr, m_sample_block+lock1_bytes, lock2_bytes);
|
|
|
|
m_dsb_secondary->Unlock(lock1_ptr, lock1_bytes, lock2_ptr, lock2_bytes);
|
|
|
|
// Update Write Pointer
|
|
m_total_samples += m_sample_block_size/sample_bytes;
|
|
m_last_write_pos += m_sample_block_size;
|
|
if (INT(m_last_write_pos) >= m_outbuf_size_max)
|
|
m_last_write_pos = m_last_write_pos - m_outbuf_size_max;
|
|
|
|
m_sample_block_size = 0;
|
|
}
|
|
}
|
|
|
|
// PLAYBACK control
|
|
DWORD dsb_status;
|
|
m_dsb_secondary->GetStatus(&dsb_status);
|
|
if((dsb_status & DSBSTATUS_PLAYING) == 0)
|
|
{
|
|
if (m_inbuf_samples >= m_outbuf_size_min)
|
|
{
|
|
m_dsb_secondary->Play(0, 0, DSBPLAY_LOOPING);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Get amount of free data in the buffer
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
DWORD CBandiVideoSound_DS::GetFreeBufferSize()
|
|
{
|
|
if(m_dsb_secondary == NULL) return 0;
|
|
|
|
if(m_total_samples == 0)
|
|
return m_outbuf_size_max;
|
|
|
|
DWORD play_pos;
|
|
DWORD free_buffer_size = 0;
|
|
if(SUCCEEDED(m_dsb_secondary->GetCurrentPosition(&play_pos, NULL)))
|
|
{
|
|
if(m_last_write_pos > play_pos)
|
|
free_buffer_size = (m_outbuf_size_max - m_last_write_pos) + play_pos;
|
|
else
|
|
free_buffer_size = play_pos - m_last_write_pos;
|
|
}
|
|
else
|
|
{
|
|
free_buffer_size = m_outbuf_size_max;
|
|
}
|
|
|
|
return free_buffer_size;
|
|
}
|