diff options
author | Daniil Cherednik <dan.cherednik@gmail.com> | 2019-12-30 00:13:23 +0300 |
---|---|---|
committer | Daniil Cherednik <dan.cherednik@gmail.com> | 2020-01-01 22:29:50 +0300 |
commit | 3d69df17657c40030fb486a86ef179a42b3873ca (patch) | |
tree | 56002a11e97322d6b6360b1479330ba83e87d731 /src/platform/win/pcm_io | |
parent | b58f4dd8353121094acdb41ad96fbdec4a580d79 (diff) | |
download | atracdenc-3d69df17657c40030fb486a86ef179a42b3873ca.tar.gz |
Initiall support of stdin reading for windows
Expected au(snd) format, 44100hz, 16bit, stereo or mono
Diffstat (limited to 'src/platform/win/pcm_io')
-rw-r--r-- | src/platform/win/pcm_io/mf/pcm_io_mf.cpp | 483 | ||||
-rw-r--r-- | src/platform/win/pcm_io/mf/pcm_io_mf.h | 26 | ||||
-rw-r--r-- | src/platform/win/pcm_io/pcm_io.cpp | 64 | ||||
-rw-r--r-- | src/platform/win/pcm_io/pcm_io_impl.h | 26 | ||||
-rw-r--r-- | src/platform/win/pcm_io/win32/pcm_io_win32.cpp | 172 | ||||
-rw-r--r-- | src/platform/win/pcm_io/win32/pcm_io_win32.h | 23 |
6 files changed, 794 insertions, 0 deletions
diff --git a/src/platform/win/pcm_io/mf/pcm_io_mf.cpp b/src/platform/win/pcm_io/mf/pcm_io_mf.cpp new file mode 100644 index 0000000..3f9ff3a --- /dev/null +++ b/src/platform/win/pcm_io/mf/pcm_io_mf.cpp @@ -0,0 +1,483 @@ +/* + * This file is part of AtracDEnc. + * + * AtracDEnc is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * AtracDEnc is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with AtracDEnc; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "../../../wav.h" +#include "../../../env.h" + +#include "pcm_io_mf.h" +#include "../pcm_io_impl.h" + +#include <comdef.h> + +#include <initguid.h> +#include <cguid.h> +#include <atlbase.h> +#include <windows.h> +#include <mfapi.h> +#include <mfidl.h> +#include <mfreadwrite.h> +#include <stdio.h> +#include <mferror.h> + +#include <propvarutil.h> + +#include <algorithm> +#include <iostream> +#include <sstream> + +#include <string> + +#pragma comment(lib, "Mfplat.lib") +#pragma comment(lib, "Mfreadwrite.lib") +#pragma comment(lib, "Propsys.lib") + +class THException : public std::exception { + static std::string hrToText(HRESULT hr) { + _com_error err(hr); + return err.ErrorMessage(); + } +public: + THException(HRESULT hr, const std::string& msg) + : std::exception((msg + ", " + hrToText(hr)).c_str()) + {} +}; + +static std::wstring Utf8ToMultiByte(const std::string& in) { + std::vector<wchar_t> buf; + int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, in.data(), in.size(), buf.data(), 0); + if (!len) { + throw std::exception("unable to convert utf8 to multiByte"); + } + buf.resize((size_t)len); + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, in.data(), in.size(), buf.data(), buf.size()); + return std::wstring(buf.data(), buf.size()); +} + +// TODO: add dither, noise shape? +static inline int16_t FloatToInt16(TFloat in) { + return std::min((int)INT16_MAX, std::max((int)INT16_MIN, (int)lrint(in * (TFloat)INT16_MAX))); +} + +static HRESULT WriteToFile(HANDLE hFile, void* p, DWORD cb) { + DWORD cbWritten = 0; + HRESULT hr = S_OK; + + BOOL bResult = WriteFile(hFile, p, cb, &cbWritten, NULL); + if (!bResult) { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + return hr; +} + +static HRESULT ConfigureAudioStream(IMFSourceReader *pReader, IMFMediaType **ppPCMAudio) { + CComPtr<IMFMediaType> pUncompressedAudioType; + CComPtr<IMFMediaType> pPartialType; + + // Select the first audio stream, and deselect all other streams. + HRESULT hr = pReader->SetStreamSelection( + (DWORD)MF_SOURCE_READER_ALL_STREAMS, FALSE); + + if (SUCCEEDED(hr)) { + hr = pReader->SetStreamSelection( + (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE); + } + + // Create a partial media type that specifies uncompressed PCM audio. + hr = MFCreateMediaType(&pPartialType); + + if (SUCCEEDED(hr)) { + hr = pPartialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); + } + + if (SUCCEEDED(hr)) { + hr = pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); + } + + // Set this type on the source reader. The source reader will + // load the necessary decoder. + if (SUCCEEDED(hr)) { + hr = pReader->SetCurrentMediaType( + (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, + NULL, pPartialType); + } + + // Get the complete uncompressed format. + if (SUCCEEDED(hr)) { + hr = pReader->GetCurrentMediaType( + (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, + &pUncompressedAudioType); + } + + // Ensure the stream is selected. + if (SUCCEEDED(hr)) { + hr = pReader->SetStreamSelection( + (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, + TRUE); + } + + // Return the PCM format to the caller. + if (SUCCEEDED(hr)) { + *ppPCMAudio = pUncompressedAudioType; + (*ppPCMAudio)->AddRef(); + } + + return hr; +} + +static HRESULT CreatePCMAudioType(UINT32 sampleRate, UINT32 bitsPerSample, UINT32 cChannels, IMFMediaType **ppType) { + HRESULT hr = S_OK; + + CComPtr<IMFMediaType> pType; + + // Calculate derived values. + UINT32 blockAlign = cChannels * (bitsPerSample / 8); + UINT32 bytesPerSecond = blockAlign * sampleRate; + + // Create the empty media type. + hr = MFCreateMediaType(&pType); + + // Set attributes on the type. + if (SUCCEEDED(hr)) { + hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); + } + + if (SUCCEEDED(hr)) { + hr = pType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); + } + + if (SUCCEEDED(hr)) { + hr = pType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, cChannels); + } + + if (SUCCEEDED(hr)) { + hr = pType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, sampleRate); + } + + if (SUCCEEDED(hr)) { + hr = pType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, blockAlign); + } + + if (SUCCEEDED(hr)) { + hr = pType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bytesPerSecond); + } + + if (SUCCEEDED(hr)) { + hr = pType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample); + } + + if (SUCCEEDED(hr)) { + hr = pType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + } + + if (SUCCEEDED(hr)) { + // Return the type to the caller. + *ppType = pType; + (*ppType)->AddRef(); + } + + return hr; +} + +class TPCMIOMediaFoundationFile : public IPCMProviderImpl { +public: + TPCMIOMediaFoundationFile(const std::string& path) { + + HRESULT hr = S_OK; + + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + + if (!SUCCEEDED(hr)) { + throw THException(hr, "unable to initialize COM library"); + } + + hr = MFStartup(MF_VERSION); + + if (!SUCCEEDED(hr)) { + throw THException(hr, "unable to initialize Media Foundation platform"); + } + + std::wstring wpath = Utf8ToMultiByte(path); + + hr = MFCreateSourceReaderFromURL(wpath.c_str(), NULL, &Reader_); + + if (FAILED(hr)) { + throw THException(hr, "qqq unable to open input file"); + } + + hr = ConfigureAudioStream(Reader_, &MediaType_); + + if (FAILED(hr)) { + throw THException(hr, "unable to get media type"); + } + + if (FAILED(MediaType_->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &ChannelsNum_))) { + throw THException(hr, "unable to get channels number"); + } + + if (FAILED(MediaType_->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &SampleRate_))) { + throw THException(hr, "unable to get sample rate"); + } + + if (FAILED(MediaType_->GetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, &BytesPerSample_))) { + throw THException(hr, "unable to get sample size"); + } + + if (!(ChannelsNum_ == 1 && BytesPerSample_ == 2 || ChannelsNum_ == 2 && BytesPerSample_ == 4)) { + throw THException(hr, "unsupported samaple format"); + } + + NEnv::SetRoundFloat(); + } + + TPCMIOMediaFoundationFile(const std::string& path, int channels, int sampleRate) { + + HRESULT hr = S_OK; + ChannelsNum_ = channels; + SampleRate_ = sampleRate; + + hr = CreatePCMAudioType(sampleRate, 16, channels, &MediaType_); + + if (FAILED(hr)) { + throw THException(hr, "unable to create PCM audio type"); + } + + UINT32 format = 0; + + WAVEFORMATEX *wav = NULL; + + hr = MFCreateWaveFormatExFromMFMediaType(MediaType_, &wav, &format); + + if (FAILED(hr)) { + throw THException(hr, "unable to create wave format"); + } + + DWORD header[] = { + // RIFF header + FCC('RIFF'), + 0, + FCC('WAVE'), + // Start of 'fmt ' chunk + FCC('fmt '), + format + }; + + DWORD dataHeader[] = { FCC('data'), 0 }; + + std::wstring wpath = Utf8ToMultiByte(path); + + OutFile = CreateFileW(wpath.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, + CREATE_ALWAYS, 0, NULL); + + if (OutFile == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + throw THException(hr, "qqq2 unable to open output file"); + } + + hr = WriteToFile(OutFile, header, sizeof(header)); + + // Write the WAVEFORMATEX structure. + if (SUCCEEDED(hr)) { + hr = WriteToFile(OutFile, wav, format); + } + + // Write the start of the 'data' chunk + + if (SUCCEEDED(hr)) { + hr = WriteToFile(OutFile, dataHeader, sizeof(dataHeader)); + } + + if (FAILED(hr)) { + throw THException(hr, "unable to write headers"); + } + + NEnv::SetRoundFloat(); + } + + ~TPCMIOMediaFoundationFile() { + if (OutFile) { + if (!CloseHandle(OutFile)) { + std::cerr << "unable to close handle" << std::endl; + } + } + } + +public: + size_t GetChannelsNum() const override { + return ChannelsNum_; + } + + size_t GetSampleRate() const override { + return SampleRate_; + } + + size_t GetTotalSamples() const override { + PROPVARIANT var; + LONGLONG duration; // duration in 100 nanosecond units + HRESULT hr = Reader_->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &var); + if (SUCCEEDED(hr)) { + hr = PropVariantToInt64(var, &duration); + PropVariantClear(&var); + } + if (!SUCCEEDED(hr)) { + throw THException(hr, "unable to extract duration from source"); + } + + const UINT32 bytesPerSecond = MFGetAttributeUINT32(MediaType_, MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 0); + const UINT32 blockSize = MFGetAttributeUINT32(MediaType_, MF_MT_AUDIO_BLOCK_ALIGNMENT, 0); + + if (!blockSize) { + throw THException(hr, "got zero block size"); + } + + const LONGLONG samplesPerSecond = bytesPerSecond / blockSize; + const LONGLONG totalSamples = samplesPerSecond * duration / 10000000; + + return static_cast<size_t>(totalSamples); + } + + size_t Read(TPCMBuffer<TFloat>& buf, size_t sz) override { + HRESULT hr = S_OK; + + const size_t sizeBytes = sz * BytesPerSample_; + + size_t curPos = 0; // position in outpuf buffer (TPCMBuffer) + + if (!Buf_.empty()) { + if (sizeBytes > (Buf_.size() - ConsummerPos_)) { + if (Buf_.size() & 0x3) { + std::cerr << "buffer misconfiguration" << std::endl; + abort(); + } + curPos = (Buf_.size() - ConsummerPos_) / BytesPerSample_; + ConvertToPcmBufferFromLE(Buf_.data() + ConsummerPos_, buf, curPos, 0, ChannelsNum_); + ConsummerPos_ = 0; + } else { + // We have all data in our buffer, just convert it and shift consumer position + ConvertToPcmBufferFromLE(Buf_.data() + ConsummerPos_, buf, sz, 0, ChannelsNum_); + ConsummerPos_ += sizeBytes; + return sz; + } + } + + bool cont = true; + while (cont) { + DWORD flags = 0; + DWORD readyToRead = 0; + CComPtr<IMFSample> sample; + + hr = Reader_->ReadSample((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, + 0, NULL, &flags, NULL, &sample); + + if (FAILED(hr)) { + break; + } + + if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) { + std::cerr << "Type change - not supported by WAVE file format.\n" << std::endl; + break; + } + + if (flags & MF_SOURCE_READERF_ENDOFSTREAM) { + std::cerr << "End of input file.\n" << std::endl; + break; + } + + if (sample == NULL) { + continue; + } + LONGLONG duration; + sample->GetSampleDuration(&duration); + + CComPtr<IMFMediaBuffer> buffer; + BYTE *audioData = NULL; + + hr = sample->ConvertToContiguousBuffer(&buffer); + + if (FAILED(hr)) { + break; + } + + hr = buffer->Lock(&audioData, NULL, &readyToRead); + + if (FAILED(hr)) { + break; + } + + if (sizeBytes > (readyToRead + (curPos * BytesPerSample_))) { + const size_t ready = readyToRead / BytesPerSample_; + ConvertToPcmBufferFromLE(audioData, buf, ready, curPos, ChannelsNum_); + curPos += ready; + } else { + ConvertToPcmBufferFromLE(audioData, buf, sz - curPos, curPos, ChannelsNum_); + size_t leftBytes = readyToRead - (sz - curPos) * BytesPerSample_; + Buf_.resize(leftBytes); + std::memcpy(Buf_.data(), audioData + (sz - curPos) * BytesPerSample_, leftBytes); + // out buffer writen completely + curPos = sz; + cont = false; + } + + hr = buffer->Unlock(); + audioData = NULL; + + if (FAILED(hr)) { + break; + } + } + + if (FAILED(hr)) { + throw THException(hr, "unable to read from PCM stream"); + } + + return curPos; + } + + size_t Write(const TPCMBuffer<TFloat>& buf, size_t sz) override { + const size_t samples = ChannelsNum_ * sz; + Buf_.resize(samples * 2); + for (size_t i = 0; i < samples; i++) { + *(int16_t*)(Buf_.data() + i * 2) = FloatToInt16(*(buf[0] + i)); + } + if (FAILED(WriteToFile(OutFile, Buf_.data(), Buf_.size()))) { + throw std::exception("unable to write PCM buffer to file"); + } + return sz; + } + +private: + CComPtr<IMFSourceReader> Reader_; + CComPtr<IMFMediaType> MediaType_; + + uint32_t ChannelsNum_; + uint32_t SampleRate_; + uint32_t BytesPerSample_; + + // Temporial buffer and consumer position + std::vector<BYTE> Buf_; + size_t ConsummerPos_ = 0; + + HANDLE OutFile = NULL; +}; + +IPCMProviderImpl* CreatePCMIOMFReadImpl(const std::string& path) { + return new TPCMIOMediaFoundationFile(path); +} + +IPCMProviderImpl* CreatePCMIOMFWriteImpl(const std::string& path, int channels, int sampleRate) { + return new TPCMIOMediaFoundationFile(path, channels, sampleRate); +}
\ No newline at end of file diff --git a/src/platform/win/pcm_io/mf/pcm_io_mf.h b/src/platform/win/pcm_io/mf/pcm_io_mf.h new file mode 100644 index 0000000..71b18d1 --- /dev/null +++ b/src/platform/win/pcm_io/mf/pcm_io_mf.h @@ -0,0 +1,26 @@ +/* + * This file is part of AtracDEnc. + * + * AtracDEnc is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * AtracDEnc is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with AtracDEnc; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include <string> + +class IPCMProviderImpl; + +IPCMProviderImpl* CreatePCMIOMFReadImpl(const std::string& path); +IPCMProviderImpl* CreatePCMIOMFWriteImpl(const std::string& path, int channels, int sampleRate);
\ No newline at end of file diff --git a/src/platform/win/pcm_io/pcm_io.cpp b/src/platform/win/pcm_io/pcm_io.cpp new file mode 100644 index 0000000..fb44cc0 --- /dev/null +++ b/src/platform/win/pcm_io/pcm_io.cpp @@ -0,0 +1,64 @@ +/* + * This file is part of AtracDEnc. + * + * AtracDEnc is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * AtracDEnc is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with AtracDEnc; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "pcm_io_impl.h" +#include "mf/pcm_io_mf.h" +#include "win32/pcm_io_win32.h" + +#include <endian.h> + +#include <iostream> +#include <windows.h> + + +void ConvertToPcmBufferFromLE(const BYTE* audioData, TPCMBuffer<TFloat>& buf, size_t sz, size_t shift, size_t channelsNum) { + if (channelsNum == 1) { + for (size_t i = 0; i < sz; i++) { + *(buf[i + shift] + 0) = (*(int16_t*)(audioData + i * 2 + 0)) / (TFloat)32768.0; + } + } else { + for (size_t i = 0; i < sz; i++) { + *(buf[i + shift] + 0) = (*(int16_t*)(audioData + i * 4 + 0)) / (TFloat)32768.0; + *(buf[i + shift] + 1) = (*(int16_t*)(audioData + i * 4 + 2)) / (TFloat)32768.0; + } + } +} + +void ConvertToPcmBufferFromBE(const BYTE* audioData, TPCMBuffer<TFloat>& buf, size_t sz, size_t shift, size_t channelsNum) { + if (channelsNum == 1) { + for (size_t i = 0; i < sz; i++) { + *(buf[i + shift] + 0) = conv_ntoh((*(int16_t*)(audioData + i * 2 + 0))) / (TFloat)32768.0; + } + } else { + for (size_t i = 0; i < sz; i++) { + *(buf[i + shift] + 0) = conv_ntoh((*(int16_t*)(audioData + i * 4 + 0))) / (TFloat)32768.0; + *(buf[i + shift] + 1) = conv_ntoh((*(int16_t*)(audioData + i * 4 + 2))) / (TFloat)32768.0; + } + } +} + +IPCMProviderImpl* CreatePCMIOReadImpl(const std::string& path) { + if (path == "-") { + return CreatePCMIOStreamWin32ReadImpl(); + } + return CreatePCMIOMFReadImpl(path); +} + +IPCMProviderImpl* CreatePCMIOWriteImpl(const std::string& path, int channels, int sampleRate) { + return CreatePCMIOMFWriteImpl(path, channels, sampleRate); +}
\ No newline at end of file diff --git a/src/platform/win/pcm_io/pcm_io_impl.h b/src/platform/win/pcm_io/pcm_io_impl.h new file mode 100644 index 0000000..117ebe0 --- /dev/null +++ b/src/platform/win/pcm_io/pcm_io_impl.h @@ -0,0 +1,26 @@ +/* + * This file is part of AtracDEnc. + * + * AtracDEnc is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * AtracDEnc is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with AtracDEnc; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "../../../wav.h" + +#include <windows.h> + +void ConvertToPcmBufferFromLE(const BYTE* audioData, TPCMBuffer<TFloat>& buf, size_t sz, size_t shift, size_t channelsNum); +void ConvertToPcmBufferFromBE(const BYTE* audioData, TPCMBuffer<TFloat>& buf, size_t sz, size_t shift, size_t channelsNum);
\ No newline at end of file diff --git a/src/platform/win/pcm_io/win32/pcm_io_win32.cpp b/src/platform/win/pcm_io/win32/pcm_io_win32.cpp new file mode 100644 index 0000000..3454bb6 --- /dev/null +++ b/src/platform/win/pcm_io/win32/pcm_io_win32.cpp @@ -0,0 +1,172 @@ +/* + * This file is part of AtracDEnc. + * + * AtracDEnc is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * AtracDEnc is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with AtracDEnc; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "../../../wav.h" +#include "../../../env.h" + +#include <endian.h> + +#include "pcm_io_win32.h" +#include "../pcm_io_impl.h" + +#include<windows.h> + +static std::string GetLastErrorAsString() +{ + DWORD errorMessageID = ::GetLastError(); + if (errorMessageID == 0) + return std::string(); + + LPSTR messageBuffer = nullptr; + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + + std::string message(messageBuffer, size); + + LocalFree(messageBuffer); + + return message; +} + +class TPCMIOStreamWin32 : public IPCMProviderImpl { +public: + TPCMIOStreamWin32() { + ReadHandle_ = GetStdHandle(STD_INPUT_HANDLE); + if (ReadHandle_ == INVALID_HANDLE_VALUE) { + const std::string msg = "Unable to open input stream " + GetLastErrorAsString(); + throw std::exception(msg.data()); + } + + static const DWORD headerSz = 24; + Buf_.resize(headerSz); + + DWORD read; + if (ReadFile(ReadHandle_, Buf_.data(), headerSz, &read, NULL)) { + if (read != headerSz) { + throw std::exception("Not enough data to determinate format."); + } + + const uint32_t offset = ParseHeader(); + if (offset < headerSz) { + throw std::exception("incorrect data offset"); + } + + uint32_t toSkip = offset - headerSz; + while (toSkip) { + char c; + bool success; + success = ReadFile(ReadHandle_, &c, 1, &read, NULL); + if (!success || !read) { + throw std::exception("Unable to seek to data position"); + } + toSkip--; + } + } else { + const std::string msg = "Unable to read header from pipe " + GetLastErrorAsString(); + throw std::exception(msg.data()); + } + } + + size_t Read(TPCMBuffer<TFloat>& buf, size_t sz) override { + if (Finished_) + return 0; + + DWORD bytesToRead = sz * 2 * ChannelsNum_; // 16 bit per sampple + Buf_.resize(bytesToRead); + + DWORD pos = 0; + + while (pos < bytesToRead) { + DWORD read; + bool success; + success = ReadFile(ReadHandle_, Buf_.data() + pos, bytesToRead - pos, &read, NULL); + if (!success || !read) { + Finished_ = true; + break; + } + pos += read; + } + + size_t toConvert = pos / 2 / ChannelsNum_; + ConvertToPcmBufferFromBE(Buf_.data(), buf, toConvert, 0, ChannelsNum_); + + return toConvert; + } + + size_t Write(const TPCMBuffer<TFloat>& buf, size_t sz) override { + abort(); + return 0; + } + + size_t GetChannelsNum() const override { + return ChannelsNum_; + } + + size_t GetSampleRate() const override { + return SampleRate_; + } + + size_t GetTotalSamples() const override { + return (size_t)-1; + } + +private: + static uint32_t ExtractValue(BYTE* buf) { + uint32_t tmp; + memcpy(&tmp, buf, 4); + return conv_ntoh(tmp); + } + + uint32_t ParseHeader() { + static const std::vector<BYTE> magic = { '.', 's', 'n', 'd' }; + if (!std::equal(Buf_.begin(), Buf_.begin() + magic.size(), magic.begin())) { + throw std::exception("Input stream must have AU(SND) format"); + } + + uint32_t dataOffset = ExtractValue(&Buf_[4]); + //uint32_t dataSize = ExtractValue(&Buf_[8]); + + uint32_t encoding = ExtractValue(&Buf_[12]); + if (encoding != 3) { + throw std::exception("Expected PCM 16 bit format"); + } + + uint32_t sampleRate = ExtractValue(&Buf_[16]); + if (sampleRate != 44100) { + throw std::exception("Expected 44100Hz sampe rate"); + } + + ChannelsNum_ = ExtractValue(&Buf_[20]); + if (ChannelsNum_ != 1 && ChannelsNum_ != 2) { + throw std::exception("Expected 1 or 2 channels"); + } + + return dataOffset; + } + + HANDLE ReadHandle_; + uint32_t ChannelsNum_ = 2; + const uint32_t SampleRate_ = 44100; + + std::vector<BYTE> Buf_; + bool Finished_ = false; +}; + +IPCMProviderImpl* CreatePCMIOStreamWin32ReadImpl() { + return new TPCMIOStreamWin32(); +}
\ No newline at end of file diff --git a/src/platform/win/pcm_io/win32/pcm_io_win32.h b/src/platform/win/pcm_io/win32/pcm_io_win32.h new file mode 100644 index 0000000..52b0c1c --- /dev/null +++ b/src/platform/win/pcm_io/win32/pcm_io_win32.h @@ -0,0 +1,23 @@ +/* + * This file is part of AtracDEnc. + * + * AtracDEnc is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * AtracDEnc is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with AtracDEnc; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +class IPCMProviderImpl; + +IPCMProviderImpl* CreatePCMIOStreamWin32ReadImpl();
\ No newline at end of file |