juicysfplugin/modules/juce_audio_formats/codecs/juce_WindowsMediaAudioFormat.cpp

362 lines
13 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace WindowsMediaCodec
{
class JuceIStream : public ComBaseClassHelper <IStream>
{
public:
JuceIStream (InputStream& in) noexcept
: ComBaseClassHelper <IStream> (0), source (in)
{
}
JUCE_COMRESULT Commit (DWORD) { return S_OK; }
JUCE_COMRESULT Write (const void*, ULONG, ULONG*) { return E_NOTIMPL; }
JUCE_COMRESULT Clone (IStream**) { return E_NOTIMPL; }
JUCE_COMRESULT SetSize (ULARGE_INTEGER) { return E_NOTIMPL; }
JUCE_COMRESULT Revert() { return E_NOTIMPL; }
JUCE_COMRESULT LockRegion (ULARGE_INTEGER, ULARGE_INTEGER, DWORD) { return E_NOTIMPL; }
JUCE_COMRESULT UnlockRegion (ULARGE_INTEGER, ULARGE_INTEGER, DWORD) { return E_NOTIMPL; }
JUCE_COMRESULT Read (void* dest, ULONG numBytes, ULONG* bytesRead)
{
auto numRead = source.read (dest, numBytes);
if (bytesRead != nullptr)
*bytesRead = numRead;
return (numRead == (int) numBytes) ? S_OK : S_FALSE;
}
JUCE_COMRESULT Seek (LARGE_INTEGER position, DWORD origin, ULARGE_INTEGER* resultPosition)
{
auto newPos = (int64) position.QuadPart;
if (origin == STREAM_SEEK_CUR)
{
newPos += source.getPosition();
}
else if (origin == STREAM_SEEK_END)
{
auto len = source.getTotalLength();
if (len < 0)
return E_NOTIMPL;
newPos += len;
}
if (resultPosition != nullptr)
resultPosition->QuadPart = newPos;
return source.setPosition (newPos) ? S_OK : E_NOTIMPL;
}
JUCE_COMRESULT CopyTo (IStream* destStream, ULARGE_INTEGER numBytesToDo,
ULARGE_INTEGER* bytesRead, ULARGE_INTEGER* bytesWritten)
{
uint64 totalCopied = 0;
int64 numBytes = numBytesToDo.QuadPart;
while (numBytes > 0 && ! source.isExhausted())
{
char buffer [1024];
auto numToCopy = (int) jmin ((int64) sizeof (buffer), (int64) numBytes);
auto numRead = source.read (buffer, numToCopy);
if (numRead <= 0)
break;
destStream->Write (buffer, numRead, nullptr);
totalCopied += numRead;
}
if (bytesRead != nullptr) bytesRead->QuadPart = totalCopied;
if (bytesWritten != nullptr) bytesWritten->QuadPart = totalCopied;
return S_OK;
}
JUCE_COMRESULT Stat (STATSTG* stat, DWORD)
{
if (stat == nullptr)
return STG_E_INVALIDPOINTER;
zerostruct (*stat);
stat->type = STGTY_STREAM;
stat->cbSize.QuadPart = jmax ((int64) 0, source.getTotalLength());
return S_OK;
}
private:
InputStream& source;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceIStream)
};
//==============================================================================
static const char* wmFormatName = "Windows Media";
static const char* const extensions[] = { ".mp3", ".wmv", ".asf", ".wm", ".wma", 0 };
//==============================================================================
class WMAudioReader : public AudioFormatReader
{
public:
WMAudioReader (InputStream* const input_)
: AudioFormatReader (input_, TRANS (wmFormatName)),
wmvCoreLib ("Wmvcore.dll")
{
JUCE_LOAD_WINAPI_FUNCTION (wmvCoreLib, WMCreateSyncReader, wmCreateSyncReader,
HRESULT, (IUnknown*, DWORD, IWMSyncReader**))
if (wmCreateSyncReader != nullptr)
{
checkCoInitialiseCalled();
HRESULT hr = wmCreateSyncReader (nullptr, WMT_RIGHT_PLAYBACK, wmSyncReader.resetAndGetPointerAddress());
if (SUCCEEDED (hr))
hr = wmSyncReader->OpenStream (new JuceIStream (*input));
if (SUCCEEDED (hr))
{
WORD streamNum = 1;
hr = wmSyncReader->GetStreamNumberForOutput (0, &streamNum);
hr = wmSyncReader->SetReadStreamSamples (streamNum, false);
scanFileForDetails();
}
}
}
~WMAudioReader()
{
if (wmSyncReader != nullptr)
wmSyncReader->Close();
}
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
int64 startSampleInFile, int numSamples) override
{
if (sampleRate <= 0)
return false;
checkCoInitialiseCalled();
clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
startSampleInFile, numSamples, lengthInSamples);
const int stride = numChannels * sizeof (int16);
while (numSamples > 0)
{
if (! bufferedRange.contains (startSampleInFile))
{
const bool hasJumped = (startSampleInFile != bufferedRange.getEnd());
if (hasJumped)
wmSyncReader->SetRange ((QWORD) (startSampleInFile * 10000000 / (int64) sampleRate), 0);
ComSmartPtr<INSSBuffer> sampleBuffer;
QWORD sampleTime, duration;
DWORD flags, outputNum;
WORD streamNum;
HRESULT hr = wmSyncReader->GetNextSample (1, sampleBuffer.resetAndGetPointerAddress(),
&sampleTime, &duration, &flags, &outputNum, &streamNum);
if (sampleBuffer != nullptr)
{
BYTE* rawData = nullptr;
DWORD dataLength = 0;
hr = sampleBuffer->GetBufferAndLength (&rawData, &dataLength);
if (dataLength == 0)
return false;
if (hasJumped)
bufferedRange.setStart ((int64) ((sampleTime * (int64) sampleRate) / 10000000));
else
bufferedRange.setStart (bufferedRange.getEnd()); // (because the positions returned often aren't continguous)
bufferedRange.setLength ((int64) (dataLength / stride));
buffer.ensureSize ((int) dataLength);
memcpy (buffer.getData(), rawData, (size_t) dataLength);
}
else if (hr == NS_E_NO_MORE_SAMPLES)
{
bufferedRange.setStart (startSampleInFile);
bufferedRange.setLength (256);
buffer.ensureSize (256 * stride);
buffer.fillWith (0);
}
else
{
return false;
}
}
auto offsetInBuffer = (int) (startSampleInFile - bufferedRange.getStart());
auto* rawData = static_cast<const int16*> (addBytesToPointer (buffer.getData(), offsetInBuffer * stride));
auto numToDo = jmin (numSamples, (int) (bufferedRange.getLength() - offsetInBuffer));
for (int i = 0; i < numDestChannels; ++i)
{
jassert (destSamples[i] != nullptr);
auto srcChan = jmin (i, (int) numChannels - 1);
const int16* src = rawData + srcChan;
int* const dst = destSamples[i] + startOffsetInDestBuffer;
for (int j = 0; j < numToDo; ++j)
{
dst[j] = ((uint32) *src) << 16;
src += numChannels;
}
}
startSampleInFile += numToDo;
startOffsetInDestBuffer += numToDo;
numSamples -= numToDo;
}
return true;
}
private:
DynamicLibrary wmvCoreLib;
ComSmartPtr<IWMSyncReader> wmSyncReader;
MemoryBlock buffer;
Range<int64> bufferedRange;
void checkCoInitialiseCalled()
{
CoInitialize (0);
}
void scanFileForDetails()
{
ComSmartPtr<IWMHeaderInfo> wmHeaderInfo;
HRESULT hr = wmSyncReader.QueryInterface (wmHeaderInfo);
if (SUCCEEDED (hr))
{
QWORD lengthInNanoseconds = 0;
WORD lengthOfLength = sizeof (lengthInNanoseconds);
WORD streamNum = 0;
WMT_ATTR_DATATYPE wmAttrDataType;
hr = wmHeaderInfo->GetAttributeByName (&streamNum, L"Duration", &wmAttrDataType,
(BYTE*) &lengthInNanoseconds, &lengthOfLength);
ComSmartPtr<IWMProfile> wmProfile;
hr = wmSyncReader.QueryInterface (wmProfile);
if (SUCCEEDED (hr))
{
ComSmartPtr<IWMStreamConfig> wmStreamConfig;
hr = wmProfile->GetStream (0, wmStreamConfig.resetAndGetPointerAddress());
if (SUCCEEDED (hr))
{
ComSmartPtr<IWMMediaProps> wmMediaProperties;
hr = wmStreamConfig.QueryInterface (wmMediaProperties);
if (SUCCEEDED (hr))
{
DWORD sizeMediaType;
hr = wmMediaProperties->GetMediaType (0, &sizeMediaType);
HeapBlock<WM_MEDIA_TYPE> mediaType;
mediaType.malloc (sizeMediaType, 1);
hr = wmMediaProperties->GetMediaType (mediaType, &sizeMediaType);
if (mediaType->majortype == WMMEDIATYPE_Audio)
{
auto* inputFormat = reinterpret_cast<WAVEFORMATEX*> (mediaType->pbFormat);
sampleRate = inputFormat->nSamplesPerSec;
numChannels = inputFormat->nChannels;
bitsPerSample = inputFormat->wBitsPerSample != 0 ? inputFormat->wBitsPerSample : 16;
lengthInSamples = (lengthInNanoseconds * (int) sampleRate) / 10000000;
}
}
}
}
}
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WMAudioReader)
};
}
//==============================================================================
WindowsMediaAudioFormat::WindowsMediaAudioFormat()
: AudioFormat (TRANS (WindowsMediaCodec::wmFormatName),
StringArray (WindowsMediaCodec::extensions))
{
}
WindowsMediaAudioFormat::~WindowsMediaAudioFormat() {}
Array<int> WindowsMediaAudioFormat::getPossibleSampleRates() { return {}; }
Array<int> WindowsMediaAudioFormat::getPossibleBitDepths() { return {}; }
bool WindowsMediaAudioFormat::canDoStereo() { return true; }
bool WindowsMediaAudioFormat::canDoMono() { return true; }
bool WindowsMediaAudioFormat::isCompressed() { return true; }
//==============================================================================
AudioFormatReader* WindowsMediaAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
{
std::unique_ptr<WindowsMediaCodec::WMAudioReader> r (new WindowsMediaCodec::WMAudioReader (sourceStream));
if (r->sampleRate > 0)
return r.release();
if (! deleteStreamIfOpeningFails)
r->input = nullptr;
return nullptr;
}
AudioFormatWriter* WindowsMediaAudioFormat::createWriterFor (OutputStream* /*streamToWriteTo*/, double /*sampleRateToUse*/,
unsigned int /*numberOfChannels*/, int /*bitsPerSample*/,
const StringPairArray& /*metadataValues*/, int /*qualityOptionIndex*/)
{
jassertfalse; // not yet implemented!
return nullptr;
}
} // namespace juce