|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2022 - Raw Material Software Limited
   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 7 End-User License
   Agreement and JUCE Privacy Policy.
   End User License Agreement: www.juce.com/juce-7-licence
   Privacy Policy: www.juce.com/juce-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 (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, (size_t) numBytes);
        if (bytesRead != nullptr)
            *bytesRead = (ULONG) 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 = (ULONGLONG) 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;
        auto numBytes = (int64) 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, (ULONG) numRead, nullptr);
            totalCopied += (ULONG) 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 = (ULONGLONG) 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", nullptr };
//==============================================================================
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() override
    {
        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 auto stride = (int) (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 * (QWORD) sampleRate) / 10000000));
                    else
                        bufferedRange.setStart (bufferedRange.getEnd()); // (because the positions returned often aren't contiguous)
                    bufferedRange.setLength ((int64) dataLength / (int64) stride);
                    buffer.ensureSize ((size_t) 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 * (size_t) 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)
            {
                JUCE_BEGIN_IGNORE_WARNINGS_MSVC (28182)
                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] = (int) (((uint32) *src) << 16);
                    src += numChannels;
                }
                JUCE_END_IGNORE_WARNINGS_MSVC
            }
            startSampleInFile += numToDo;
            startOffsetInDestBuffer += numToDo;
            numSamples -= numToDo;
        }
        return true;
    }
private:
    DynamicLibrary wmvCoreLib;
    ComSmartPtr<IWMSyncReader> wmSyncReader;
    MemoryBlock buffer;
    Range<int64> bufferedRange;
    void checkCoInitialiseCalled()
    {
        ignoreUnused (CoInitialize (nullptr));
    }
    void scanFileForDetails()
    {
        if (auto wmHeaderInfo = wmSyncReader.getInterface<IWMHeaderInfo>())
        {
            QWORD lengthInNanoseconds = 0;
            WORD lengthOfLength = sizeof (lengthInNanoseconds);
            WORD streamNum = 0;
            WMT_ATTR_DATATYPE wmAttrDataType;
            wmHeaderInfo->GetAttributeByName (&streamNum, L"Duration", &wmAttrDataType,
                                              (BYTE*) &lengthInNanoseconds, &lengthOfLength);
            if (auto wmProfile = wmSyncReader.getInterface<IWMProfile>())
            {
                ComSmartPtr<IWMStreamConfig> wmStreamConfig;
                auto hr = wmProfile->GetStream (0, wmStreamConfig.resetAndGetPointerAddress());
                if (SUCCEEDED (hr))
                {
                    if (auto wmMediaProperties = wmStreamConfig.getInterface<IWMMediaProps>())
                    {
                        DWORD sizeMediaType;
                        hr = wmMediaProperties->GetMediaType (nullptr, &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 * (QWORD) 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
 |