#include "captions-mssapi-stream.hpp" #include "captions-mssapi.hpp" #include #include #include #include using namespace std; #if 0 #define debugfunc(format, ...) \ blog(LOG_DEBUG, "[Captions] %s(" format ")", __FUNCTION__, \ ##__VA_ARGS__) #else #define debugfunc(format, ...) #endif CaptionStream::CaptionStream(DWORD samplerate_, mssapi_captions *handler_) : handler(handler_), samplerate(samplerate_), event(CreateEvent(nullptr, false, false, nullptr)) { buf_info.ulMsMinNotification = 50; buf_info.ulMsBufferSize = 500; buf_info.ulMsEventBias = 0; format.wFormatTag = WAVE_FORMAT_PCM; format.nChannels = 1; format.nSamplesPerSec = 16000; format.nAvgBytesPerSec = format.nSamplesPerSec * sizeof(uint16_t); format.nBlockAlign = 2; format.wBitsPerSample = 16; format.cbSize = sizeof(format); } void CaptionStream::Stop() { { lock_guard lock(m); circlebuf_free(buf); } cv.notify_one(); } void CaptionStream::PushAudio(const void *data, size_t frames) { bool ready = false; lock_guard lock(m); circlebuf_push_back(buf, data, frames * sizeof(int16_t)); write_pos += frames * sizeof(int16_t); if (wait_size && buf->size >= wait_size) ready = true; if (ready) cv.notify_one(); } // IUnknown methods STDMETHODIMP CaptionStream::QueryInterface(REFIID riid, void **ppv) { if (riid == IID_IUnknown) { AddRef(); *ppv = this; } else if (riid == IID_IStream) { AddRef(); *ppv = (IStream *)this; } else if (riid == IID_ISpStreamFormat) { AddRef(); *ppv = (ISpStreamFormat *)this; } else if (riid == IID_ISpAudio) { AddRef(); *ppv = (ISpAudio *)this; } else { *ppv = nullptr; return E_NOINTERFACE; } return NOERROR; } STDMETHODIMP_(ULONG) CaptionStream::AddRef() { return (ULONG)os_atomic_inc_long(&refs); } STDMETHODIMP_(ULONG) CaptionStream::Release() { ULONG new_refs = (ULONG)os_atomic_dec_long(&refs); if (!new_refs) delete this; return new_refs; } // ISequentialStream methods STDMETHODIMP CaptionStream::Read(void *data, ULONG bytes, ULONG *read_bytes) { HRESULT hr = S_OK; size_t cur_size; debugfunc("data, %lu, read_bytes", bytes); if (!data) return STG_E_INVALIDPOINTER; { lock_guard lock1(m); wait_size = bytes; cur_size = buf->size; } unique_lock lock(m); if (bytes > cur_size) cv.wait(lock); if (bytes > (ULONG)buf->size) { bytes = (ULONG)buf->size; hr = S_FALSE; } if (bytes) circlebuf_pop_front(buf, data, bytes); if (read_bytes) *read_bytes = bytes; wait_size = 0; pos.QuadPart += bytes; return hr; } STDMETHODIMP CaptionStream::Write(const void *, ULONG bytes, ULONG *) { debugfunc("data, %lu, written_bytes", bytes); UNUSED_PARAMETER(bytes); return STG_E_INVALIDFUNCTION; } // IStream methods STDMETHODIMP CaptionStream::Seek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *new_pos) { debugfunc("%lld, %lx, new_pos", move, origin); UNUSED_PARAMETER(move); UNUSED_PARAMETER(origin); if (!new_pos) return E_POINTER; if (origin != SEEK_CUR || move.QuadPart != 0) return E_NOTIMPL; *new_pos = pos; return S_OK; } STDMETHODIMP CaptionStream::SetSize(ULARGE_INTEGER new_size) { debugfunc("%llu", new_size); UNUSED_PARAMETER(new_size); return STG_E_INVALIDFUNCTION; } STDMETHODIMP CaptionStream::CopyTo(IStream *stream, ULARGE_INTEGER bytes, ULARGE_INTEGER *read_bytes, ULARGE_INTEGER *written_bytes) { HRESULT hr; debugfunc("stream, %llu, read_bytes, written_bytes", bytes); if (!stream) return STG_E_INVALIDPOINTER; ULONG written = 0; if (bytes.QuadPart > (ULONGLONG)buf->size) bytes.QuadPart = (ULONGLONG)buf->size; lock_guard lock(m); temp_buf.resize((size_t)bytes.QuadPart); circlebuf_peek_front(buf, &temp_buf[0], (size_t)bytes.QuadPart); hr = stream->Write(temp_buf.data(), (ULONG)bytes.QuadPart, &written); if (read_bytes) *read_bytes = bytes; if (written_bytes) written_bytes->QuadPart = written; return hr; } STDMETHODIMP CaptionStream::Commit(DWORD commit_flags) { debugfunc("%lx", commit_flags); UNUSED_PARAMETER(commit_flags); /* TODO? */ return S_OK; } STDMETHODIMP CaptionStream::Revert(void) { debugfunc(""); return S_OK; } STDMETHODIMP CaptionStream::LockRegion(ULARGE_INTEGER offset, ULARGE_INTEGER size, DWORD type) { debugfunc("%llu, %llu, %ld", offset, size, type); UNUSED_PARAMETER(offset); UNUSED_PARAMETER(size); UNUSED_PARAMETER(type); /* TODO? */ return STG_E_INVALIDFUNCTION; } STDMETHODIMP CaptionStream::UnlockRegion(ULARGE_INTEGER offset, ULARGE_INTEGER size, DWORD type) { debugfunc("%llu, %llu, %ld", offset, size, type); UNUSED_PARAMETER(offset); UNUSED_PARAMETER(size); UNUSED_PARAMETER(type); /* TODO? */ return STG_E_INVALIDFUNCTION; } static const wchar_t *stat_name = L"Caption stream"; STDMETHODIMP CaptionStream::Stat(STATSTG *stg, DWORD flag) { debugfunc("stg, %lu", flag); if (!stg) return E_POINTER; lock_guard lock(m); *stg = {}; stg->type = STGTY_STREAM; stg->cbSize.QuadPart = (ULONGLONG)buf->size; if (flag == STATFLAG_DEFAULT) { size_t byte_size = (wcslen(stat_name) + 1) * sizeof(wchar_t); stg->pwcsName = (wchar_t *)CoTaskMemAlloc(byte_size); memcpy(stg->pwcsName, stat_name, byte_size); } return S_OK; } STDMETHODIMP CaptionStream::Clone(IStream **stream) { debugfunc("stream"); *stream = nullptr; return E_NOTIMPL; } // ISpStreamFormat methods STDMETHODIMP CaptionStream::GetFormat(GUID *guid, WAVEFORMATEX **co_mem_wfex_out) { debugfunc("guid, co_mem_wfex_out"); if (!guid || !co_mem_wfex_out) return E_POINTER; if (format.wFormatTag == 0) { *co_mem_wfex_out = nullptr; return S_OK; } void *wfex = CoTaskMemAlloc(sizeof(format)); memcpy(wfex, &format, sizeof(format)); *co_mem_wfex_out = (WAVEFORMATEX *)wfex; return S_OK; } // ISpAudio methods STDMETHODIMP CaptionStream::SetState(SPAUDIOSTATE state_, ULONGLONG) { debugfunc("%lu, reserved", state_); state = state_; return S_OK; } STDMETHODIMP CaptionStream::SetFormat(REFGUID guid_ref, const WAVEFORMATEX *wfex) { debugfunc("guid, wfex"); if (!wfex) return E_INVALIDARG; if (guid_ref == SPDFID_WaveFormatEx) { lock_guard lock(m); memcpy(&format, wfex, sizeof(format)); if (!handler->reset_resampler(AUDIO_FORMAT_16BIT, wfex->nSamplesPerSec)) return E_FAIL; /* 50 msec */ DWORD size = format.nSamplesPerSec / 20; DWORD byte_size = size * format.nBlockAlign; circlebuf_reserve(buf, (size_t)byte_size); } return S_OK; } STDMETHODIMP CaptionStream::GetStatus(SPAUDIOSTATUS *status) { debugfunc("status"); if (!status) return E_POINTER; /* TODO? */ lock_guard lock(m); *status = {}; status->cbNonBlockingIO = (ULONG)buf->size; status->State = state; status->CurSeekPos = pos.QuadPart; status->CurDevicePos = write_pos; return S_OK; } STDMETHODIMP CaptionStream::SetBufferInfo(const SPAUDIOBUFFERINFO *buf_info_) { debugfunc("buf_info"); /* TODO */ buf_info = *buf_info_; return S_OK; } STDMETHODIMP CaptionStream::GetBufferInfo(SPAUDIOBUFFERINFO *buf_info_) { debugfunc("buf_info"); if (!buf_info_) return E_POINTER; *buf_info_ = buf_info; return S_OK; } STDMETHODIMP CaptionStream::GetDefaultFormat(GUID *format, WAVEFORMATEX **co_mem_wfex_out) { debugfunc("format, co_mem_wfex_out"); if (!format || !co_mem_wfex_out) return E_POINTER; void *wfex = CoTaskMemAlloc(sizeof(format)); memcpy(wfex, &format, sizeof(format)); *format = SPDFID_WaveFormatEx; *co_mem_wfex_out = (WAVEFORMATEX *)wfex; return S_OK; } STDMETHODIMP_(HANDLE) CaptionStream::EventHandle(void) { debugfunc(""); return event; } STDMETHODIMP CaptionStream::GetVolumeLevel(ULONG *level) { debugfunc("level"); if (!level) return E_POINTER; *level = vol; return S_OK; } STDMETHODIMP CaptionStream::SetVolumeLevel(ULONG level) { debugfunc("%lu", level); vol = level; return S_OK; } STDMETHODIMP CaptionStream::GetBufferNotifySize(ULONG *size) { debugfunc("size"); if (!size) return E_POINTER; *size = notify_size; return S_OK; } STDMETHODIMP CaptionStream::SetBufferNotifySize(ULONG size) { debugfunc("%lu", size); notify_size = size; return S_OK; }