blob: 132b455e17de66a396ab01fbfacc4db4c23708be [file] [log] [blame]
// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
#include <windows.h>
#include <mmreg.h>
#include <cassert>
#include <vector>
#include "clockable.h"
#include "debugutil.h"
#include "eventutil.h"
#include "memutil.h"
#include "threadutil.h"
#include "webmdsound.h"
namespace WebmDirectX
{
const UINT32 kF32BytesPerSample = sizeof(float);
const UINT32 kF32BitsPerSample = kF32BytesPerSample * 8;
const UINT32 kS16BytesPerSample = sizeof(INT16);
const UINT32 kS16BitsPerSample = kS16BytesPerSample * 8;
// Note: AudioBufferTemplate is not in use!!
template <class SampleType>
AudioBufferTemplate<SampleType>::AudioBufferTemplate():
sample_size_(sizeof SampleType)
{
DBGLOG("ctor, sample_size_=" << sample_size_);
}
template <class SampleType>
AudioBufferTemplate<SampleType>::~AudioBufferTemplate()
{
DBGLOG("dtor");
}
template <class SampleType>
HRESULT AudioBufferTemplate<SampleType>::Read(UINT32 out_buf_size,
UINT32* ptr_bytes_written,
SampleType* ptr_samples)
{
if (!out_buf_size || !ptr_bytes_written || !ptr_samples)
{
return E_INVALIDARG;
}
if (audio_buf_.empty())
{
DBGLOG("buffer empty");
return S_FALSE;
}
UINT32 aud_bytes_available = SamplesToBytes(audio_buf_.size());
UINT32 bytes_to_copy = out_buf_size >= aud_bytes_available ?
aud_bytes_available : out_buf_size;
void* ptr_out_data = reinterpret_cast<void*>(ptr_samples);
HRESULT hr = ::memcpy_s(ptr_out_data, max_bytes, &audio_buf_[0],
bytes_to_copy);
if (SUCCEEDED(hr))
{
UINT32 samples_to_erase = BytesToSamples(bytes_to_copy);
audio_buf_.erase(audio_buf_[0], audio_buf_[samples_to_erase]);
}
return hr;
}
template <class SampleType>
HRESULT AudioBufferTemplate<SampleType>::Write(SampleType* ptr_samples,
UINT32 length_in_bytes)
{
if (!ptr_samples || !length_in_bytes)
{
return E_INVALIDARG;
}
UINT32 num_samples = BytesToSamples(length_in_bytes);
audio_buf_.insert(audio_buf_.end(), num_samples, ptr_samples);
return hr;
}
AudioBuffer::AudioBuffer():
sample_size_(0)
{
DBGLOG("ctor");
}
AudioBuffer::~AudioBuffer()
{
DBGLOG("dtor");
}
F32AudioBuffer::F32AudioBuffer()
{
AudioBuffer::sample_size_ = kF32BytesPerSample;
assert(kF32BytesPerSample == 4);
DBGLOG("ctor");
}
F32AudioBuffer::~F32AudioBuffer()
{
DBGLOG("dtor");
}
HRESULT F32AudioBuffer::Read(UINT32 out_buf_size, UINT32* ptr_bytes_written,
void* ptr_samples)
{
if (!out_buf_size || !ptr_bytes_written || !ptr_samples)
{
return E_INVALIDARG;
}
if (audio_buf_.empty())
{
DBGLOG("buffer empty");
return S_FALSE;
}
UINT64 aud_bytes_available = SamplesToBytes(audio_buf_.size());
UINT64 bytes_to_copy = out_buf_size >= aud_bytes_available ?
aud_bytes_available : out_buf_size;
void* ptr_out_data = reinterpret_cast<void*>(ptr_samples);
HRESULT hr = ::memcpy_s(ptr_out_data, out_buf_size, &audio_buf_[0],
bytes_to_copy);
if (SUCCEEDED(hr))
{
UINT64 samples_to_erase = BytesToSamples(bytes_to_copy);
audio_buf_.erase(audio_buf_.begin(), audio_buf_.begin()+samples_to_erase);
}
return hr;
}
HRESULT F32AudioBuffer::Write(const void* const ptr_samples,
UINT32 length_in_bytes,
UINT32* ptr_samples_written)
{
if (!ptr_samples || !length_in_bytes || !ptr_samples_written)
{
return E_INVALIDARG;
}
UINT64 num_samples = BytesToSamples(length_in_bytes);
typedef const float* const f32_read_ptr;
f32_read_ptr ptr_fp_samples = reinterpret_cast<f32_read_ptr>(ptr_samples);
audio_buf_.insert(audio_buf_.end(), num_samples, *ptr_fp_samples);
*ptr_samples_written = static_cast<UINT32>(num_samples);
return S_OK;
}
S16AudioBuffer::S16AudioBuffer()
{
AudioBuffer::sample_size_ = kS16BytesPerSample;
assert(kS16BytesPerSample == 2);
DBGLOG("ctor");
}
S16AudioBuffer::~S16AudioBuffer()
{
DBGLOG("dtor");
}
HRESULT S16AudioBuffer::Read(UINT32 out_buf_size, UINT32* ptr_bytes_written,
void* ptr_samples)
{
if (!out_buf_size || !ptr_bytes_written || !ptr_samples)
{
return E_INVALIDARG;
}
if (audio_buf_.empty())
{
DBGLOG("buffer empty");
return S_FALSE;
}
UINT64 aud_bytes_available = SamplesToBytes(audio_buf_.size());
UINT64 bytes_to_copy = out_buf_size >= aud_bytes_available ?
aud_bytes_available : out_buf_size;
void* ptr_out_data = reinterpret_cast<void*>(ptr_samples);
HRESULT hr = ::memcpy_s(ptr_out_data, out_buf_size, &audio_buf_[0],
bytes_to_copy);
if (SUCCEEDED(hr))
{
UINT64 samples_to_erase = BytesToSamples(bytes_to_copy);
audio_buf_.erase(audio_buf_.begin(),
audio_buf_.begin()+samples_to_erase);
}
return hr;
}
HRESULT S16AudioBuffer::Write(const void* const ptr_samples,
UINT32 length_in_bytes,
UINT32* ptr_samples_written)
{
if (!ptr_samples || !length_in_bytes || !ptr_samples_written)
{
return E_INVALIDARG;
}
UINT64 num_samples = BytesToSamples(length_in_bytes);
typedef const INT16* const s16_read_ptr;
s16_read_ptr ptr_s16_samples = reinterpret_cast<s16_read_ptr>(ptr_samples);
audio_buf_.insert(audio_buf_.end(), num_samples, *ptr_s16_samples);
*ptr_samples_written = (UINT32)num_samples;
return S_OK;
}
AudioPlaybackDevice::AudioPlaybackDevice():
dsound_buffer_size_(0),
hwnd_(NULL),
play_cursor_(0),
ptr_dsound_(NULL),
ptr_dsound_buf_(NULL),
ptr_dsound_thread_event_(NULL),
ptr_dsound_thread_(NULL),
samples_buffered_(0),
samples_played_(0),
state_(STATE_STOPPED)
{
}
AudioPlaybackDevice::~AudioPlaybackDevice()
{
WebmUtil::safe_rel(ptr_dsound_);
WebmUtil::safe_rel(ptr_dsound_buf_);
}
HRESULT AudioPlaybackDevice::Open(HWND hwnd,
const WAVEFORMATEXTENSIBLE* const ptr_wfx)
{
HRESULT hr;
CHK(hr, DirectSoundCreate8(NULL /* same as DSDEVID_DefaultPlayback */,
&ptr_dsound_, NULL));
if (FAILED(hr))
{
return hr;
}
if (!hwnd)
{
HWND desktop_hwnd = GetDesktopWindow();
// TODO(tomfinegan): Using |desktop_hwnd| is wrong, we need our own
// window here. Using the desktop window means that
// users are stuck hearing our audio when the desktop
// window is active, and might not be able to hear
// anything when our own window is active.
hwnd_ = desktop_hwnd;
}
CHK(hr, ptr_dsound_->SetCooperativeLevel(hwnd_, DSSCL_PRIORITY));
if (FAILED(hr))
{
return hr;
}
CHK(hr, CreateAudioBuffer_(ptr_wfx->Format.wFormatTag,
ptr_wfx->Format.wBitsPerSample));
if (FAILED(hr))
{
return hr;
}
CHK(hr, CreateDirectSoundBuffer_(ptr_wfx));
if (FAILED(hr))
{
return hr;
}
return hr;
}
HRESULT AudioPlaybackDevice::Start()
{
if (STATE_STOPPED != state_)
{
DBGLOG("ERROR Already started.");
return E_UNEXPECTED;
}
// Create the event we'll use to control |DSoundWriterThread_|
using WebmMfUtil::EventWaiter;
ptr_dsound_thread_event_.reset(new (std::nothrow) EventWaiter());
if (!ptr_dsound_thread_event_.get())
{
DBGLOG("ERROR no memory for thread event.");
return E_OUTOFMEMORY;
}
HRESULT hr;
CHK(hr, ptr_dsound_thread_event_->Create());
if (FAILED(hr))
{
return hr;
}
// Create the thread, |ptr_dsound_thread_|
using WebmMfUtil::SimpleThread;
ptr_dsound_thread_.reset(new (std::nothrow) WebmMfUtil::SimpleThread());
if (!ptr_dsound_thread_.get())
{
DBGLOG("ERROR no memory for thread.");
return E_OUTOFMEMORY;
}
// Start the thread
CHK(hr, ptr_dsound_thread_->Run(DSoundWriterThread_,
reinterpret_cast<void*>(this)));
if (SUCCEEDED(hr))
{
state_ = STATE_STARTED;
}
return hr;
}
HRESULT AudioPlaybackDevice::Stop()
{
if (state_ == STATE_STOPPED)
{
DBGLOG("ERROR Already stopped.");
return E_UNEXPECTED;
}
HRESULT hr = S_OK;
if (STATE_PLAY == state_)
{
CHK(hr, Pause());
if (FAILED(hr))
{
return hr;
}
}
if (ptr_dsound_thread_->Running())
{
// tell the |DSoundWriterThread_| to stop
CHK(hr, ptr_dsound_thread_event_->Set());
if (FAILED(hr))
{
return hr;
}
// wait for |DSoundWriterThread_| to signal
CHK(hr, ptr_dsound_thread_event_->Wait());
if (FAILED(hr))
{
return hr;
}
state_ = STATE_STOPPED;
}
return hr;
}
HRESULT AudioPlaybackDevice::Pause()
{
if (STATE_PLAY != state_)
{
DBGLOG("ERROR wrong state, not playing.");
return E_UNEXPECTED;
}
HRESULT hr;
CHK(hr, ptr_dsound_buf_->Stop());
if (SUCCEEDED(hr))
{
state_ = STATE_PAUSE;
}
return hr;
}
HRESULT AudioPlaybackDevice::Play()
{
bool wrong_state = (STATE_PAUSE != state_ && STATE_STARTED != state_);
if (wrong_state)
{
DBGLOG("ERROR wrong state.");
return E_UNEXPECTED;
}
HRESULT hr;
CHK(hr, ptr_dsound_buf_->Play(0, 0, DSBPLAY_LOOPING));
if (SUCCEEDED(hr))
{
state_ = STATE_PLAY;
}
return hr;
}
HRESULT AudioPlaybackDevice::WriteAudioBuffer(const void* const ptr_samples,
UINT32 length_in_bytes)
{
if (!ptr_audio_buf_.get() || !ptr_audio_buf_->GetSampleSize())
{
DBGLOG("ERROR not configured");
return E_UNEXPECTED;
}
if (!ptr_samples || !length_in_bytes)
{
DBGLOG("ERROR bad arg(s)");
return E_INVALIDARG;
}
if (length_in_bytes < ptr_audio_buf_->GetSampleSize())
{
DBGLOG("ERROR less than 1 sample in user input buffer");
return E_INVALIDARG;
}
Lock lock;
HRESULT hr = S_OK;
CHK(hr, lock.Seize(this));
if (FAILED(hr))
{
return hr;
}
UINT32 samples_written = 0;
CHK(hr, ptr_audio_buf_->Write(ptr_samples, length_in_bytes,
&samples_written));
if (SUCCEEDED(hr))
{
samples_buffered_ += samples_written;
}
return hr;
}
HRESULT AudioPlaybackDevice::WriteDSoundBuffer_()
{
if (!ptr_audio_buf_.get() || !ptr_dsound_buf_)
{
DBGLOG("ERROR not configured");
return E_UNEXPECTED;
}
Lock lock;
HRESULT hr = S_OK;
CHK(hr, lock.Seize(this));
if (FAILED(hr))
{
return hr;
}
UINT32 bytes_available = 0;
UINT32 samples_available = 0;
CHK(hr, ptr_audio_buf_->Available(&samples_available, &bytes_available));
if (FAILED(hr))
{
return hr;
}
if (!samples_available)
{
DBGLOG("no samples in buffer");
return S_FALSE;
}
// adjust |bytes_available| to ensure we avoid trying to lock a portion
// of |ptr_dsound_buf_| that's larger than the entire buffer
bytes_available = bytes_available > dsound_buffer_size_ ?
dsound_buffer_size_ : bytes_available;
// We own our internal lock, try to lock the dsound buffer...
DWORD write_offset = 0; // ignored by dsound because we set the
// DSBLOCK_FROMWRITECURSOR flag
// DirectSound buffers are circular, so we might get two write pointers
// back. When we do, we must write to both if Lock gives us two non-null
// pointers to satisfy our |length_in_bytes| requirement.
void* ptr_write1 = NULL;
void* ptr_write2 = NULL;
DWORD write_space1 = 0;
DWORD write_space2 = 0;
// Always lock the dsound buffer at the current write cursor position
DWORD lock_flags = DSBLOCK_FROMWRITECURSOR;
CHK(hr, ptr_dsound_buf_->Lock(write_offset, bytes_available, &ptr_write1,
&write_space1, &ptr_write2, &write_space2,
lock_flags));
if (FAILED(hr))
{
DBGLOG("ERROR Lock failed.");
return hr;
}
bool buffer_full = (write_space1 + write_space2) == 0;
if (buffer_full)
{
UpdateSamplesPlayed_();
return S_FALSE;
}
UINT32 bytes_written1 = 0;
if (ptr_write1)
{
const UINT32 bytes_to_write =
write_space1 > bytes_available ? bytes_available : write_space1;
CHK(hr, ptr_audio_buf_->Read(bytes_to_write, &bytes_written1,
ptr_write1));
}
UINT32 bytes_written2 = 0;
UINT32 bytes_left = bytes_available - write_space1;
if (ptr_write2 && bytes_available > write_space1 && bytes_left)
{
CHK(hr, ptr_audio_buf_->Read(write_space2, &bytes_written2,
ptr_write2));
}
CHK(hr, ptr_dsound_buf_->Unlock(ptr_write1, bytes_written1, ptr_write2,
bytes_written2));
UpdateSamplesPlayed_();
//DBGLOG("bytes_written1=" << bytes_written1
// << "bytes_written2=" << bytes_written2
// << "total bytes written=" << bytes_written1 + bytes_written2);
return hr;
}
void AudioPlaybackDevice::UpdateSamplesPlayed_()
{
DWORD play_cursor = 0, write_cursor = 0;
// get |ptr_dsound_buf_| cursor positions
HRESULT hr;
CHK(hr, ptr_dsound_buf_->GetCurrentPosition(&play_cursor, &write_cursor));
UINT64 bytes_played = 0;
if (play_cursor < play_cursor_)
{
// wrapped
bytes_played = play_cursor + play_cursor_ - dsound_buffer_size_;
}
else
{
bytes_played = play_cursor - play_cursor_;
}
if (bytes_played && bytes_played >= ptr_audio_buf_->GetSampleSize())
{
// store total samples played in |samples_played_| for user playback
// timing
UINT64 samples_played = ptr_audio_buf_->BytesToSamples(bytes_played);
samples_played_ += samples_played;
//DBGLOG("samples_played=" << samples_played);
}
//DBGLOG("total samples_played_=" << samples_played_);
play_cursor_ = play_cursor;
}
DWORD AudioPlaybackDevice::DSoundWriterThread_(void* ptr_this)
{
if (!ptr_this)
{
DBGLOG("ERROR NULL thread data pointer");
return EXIT_FAILURE;
}
AudioPlaybackDevice* ptr_apd =
reinterpret_cast<AudioPlaybackDevice*>(ptr_this);
WebmMfUtil::EventWaiter* apd_event =
ptr_apd->ptr_dsound_thread_event_.get();
HRESULT hr;
for (;;)
{
if (S_OK == apd_event->ZeroWait())
{
// received a message, at present that means it's time to stop
CHK(hr, apd_event->Set());
break;
}
if (STATE_PLAY == ptr_apd->state_)
{
// we intentionally ignore the return values other than S_FALSE from
// |WriteDsoundBuffer_|, though we log failures for sanity's sake in
// debug mode
CHK(hr, ptr_apd->WriteDSoundBuffer_());
// and now we yield
if (S_FALSE == hr)
{
// direct sound buffer full, let it play out for a bit
Sleep(1);
}
else
{
Sleep(0);
}
}
else
{
// paused or stopping, just sleep for a millisecond
Sleep(1);
}
}
return EXIT_SUCCESS;
}
HRESULT AudioPlaybackDevice::CreateAudioBuffer_(WORD fmt_tag, WORD bits)
{
if (WAVE_FORMAT_PCM != fmt_tag && WAVE_FORMAT_IEEE_FLOAT != fmt_tag)
{
DBGLOG("ERROR unsupported format tag");
return E_INVALIDARG;
}
// Create our internal audio buffer based on input sample type
// (we support only S16 and float samples)
if (WAVE_FORMAT_PCM == fmt_tag && kS16BitsPerSample == bits)
{
ptr_audio_buf_.reset(new (std::nothrow) S16AudioBuffer());
}
else if (WAVE_FORMAT_IEEE_FLOAT == fmt_tag && kF32BitsPerSample == bits)
{
ptr_audio_buf_.reset(new (std::nothrow) F32AudioBuffer());
}
else
{
DBGLOG("ERROR unsupported sample size");
return E_INVALIDARG;
}
return ptr_audio_buf_.get() ? S_OK : E_OUTOFMEMORY;
}
HRESULT AudioPlaybackDevice::CreateDirectSoundBuffer_(
const WAVEFORMATEXTENSIBLE* const ptr_wfx)
{
if (!ptr_wfx)
{
DBGLOG("NULL WAVEFORMATEX!");
return E_INVALIDARG;
}
if (!ptr_dsound_)
{
DBGLOG("called without valid IDirectSound pointer!");
return E_UNEXPECTED;
}
const WORD fmt_tag = ptr_wfx->Format.wFormatTag;
if (WAVE_FORMAT_PCM != fmt_tag && WAVE_FORMAT_IEEE_FLOAT != fmt_tag)
{
DBGLOG("unsupported format tag!");
return E_INVALIDARG;
}
DSBUFFERDESC aud_buffer_desc = {0};
aud_buffer_desc.dwSize = sizeof DSBUFFERDESC;
aud_buffer_desc.guid3DAlgorithm = DS3DALG_DEFAULT;
aud_buffer_desc.lpwfxFormat = (WAVEFORMATEX*)ptr_wfx;
dsound_buffer_size_ = ptr_wfx->Format.nAvgBytesPerSec;
aud_buffer_desc.dwBufferBytes = dsound_buffer_size_;
aud_buffer_desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
// Obtain our IDirectSoundBuffer8 interface pointer, |ptr_dsound_buf_|, by:
// 1. Create an IDirectSoundBuffer.
// 2. Call QueryInterface on the IDirectSoundBuffer instance to obtain the
// IDirectSoundBuffer8 instance.
HRESULT hr;
IDirectSoundBuffer* ptr_dsbuf;
CHK(hr, ptr_dsound_->CreateSoundBuffer(&aud_buffer_desc, &ptr_dsbuf, NULL));
if (FAILED(hr) || !ptr_dsbuf)
{
return hr;
}
void* ptr_dsound_buf8 = reinterpret_cast<void*>(ptr_dsound_buf_);
CHK(hr, ptr_dsbuf->QueryInterface(IID_IDirectSoundBuffer8,
&ptr_dsound_buf8));
if (FAILED(hr))
{
return hr;
}
return hr;
}
} // WebmDirectX namespace