OggStream

昨日のOggサンプルをStreamクラスで実装してみた。

#include <windows.h>
#include <stdio.h>
#include <math.h>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>

#using <System.dll>

using namespace System;
using namespace System::IO;
using namespace System::Media;

#pragma pack (push, 1)
typedef struct {
    short   wFormatTag;
    unsigned short wChannels;
    unsigned long dwSamplesPerSec;
    unsigned long dwAvgBytesVerSec;
    unsigned short wBlockAlign;
    unsigned short wBitsPerSample;
} FormatChunk;

typedef struct {
    char cRIFF[4];
    int iSizeRIFF;
    char cType[4];
    char cFmt[4];
    long chunkSize;
    FormatChunk chunk;
    char cData[4];
    int iSizeData;
} WAVEFILEHEADER;
#pragma pack (pop)

ref class OggStream : Stream {
    WAVEFILEHEADER* header;
    OggVorbis_File* vf;
    vorbis_info* vi;
    int word;
    double sec;
    long long position;
    bool loaded;

    void release () {
        if (vf) {
            ov_clear (vf);
            delete vf;
            vf = NULL;
        }
        if (vi)
            vi = NULL;
        if (header) {
            delete header;
            header = NULL;
        }
        sec = 0.0;
        position = 0;
        loaded = false;
    }

public:
    OggStream (const char* path) : word(2), sec(0.0), position(0), loaded (false) {
        FILE* fp;
        errno_t err;
        err = fopen_s (&fp, path, "rb");
        if (err != 0) 
            throw gcnew ApplicationException;

        vf = new OggVorbis_File;
        if (ov_open (fp, vf, NULL, 0) < 0) {
            fclose (fp);
            delete vf;
            vf = NULL;
            throw gcnew ApplicationException;
        }

        vi = ov_info (vf, -1);
        if (vi == NULL) {
            release ();
            throw gcnew ApplicationException;
        }

        header = new WAVEFILEHEADER;
        sec = ov_time_total (vf, -1);
        header->iSizeData = (LONG)ceil(vi->channels * vi->rate * sec * word);
        memcpy (header->cRIFF, "RIFF", 4);
        header->iSizeRIFF = sizeof (WAVEFILEHEADER) + header->iSizeData - 8;
        memcpy (header->cType, "WAVE", 4);
        memcpy (header->cFmt, "fmt ", 4);

        header->chunkSize = sizeof (FormatChunk);
        header->chunk.wFormatTag = WAVE_FORMAT_PCM;
        header->chunk.wChannels = vi->channels;
        header->chunk.dwSamplesPerSec = vi->rate;
        header->chunk.dwAvgBytesVerSec= vi->rate * vi->channels * word;
        header->chunk.wBlockAlign = vi->channels * word;
        header->chunk.wBitsPerSample = word * 8;
        memcpy (header->cData, "data", 4);
    }

    ~OggStream () {
        release ();
    }

    !OggStream () {
        release ();
    }

    virtual property bool CanRead {
        bool get () override { return true; }
    }

    virtual property bool CanWrite {
        bool get () override { return false; }
    }

    virtual property bool CanSeek {
        bool get () override { return false; }
    }

    virtual property long long Length {
        long long get () override { return header == NULL ? 0 : header->iSizeData; }
    }

    virtual property long long Position {
        long long get () override { return position; }
        void set (long long value) override { throw gcnew NotSupportedException; }
    }

    virtual void Flush () override {}

    virtual int Read (array<byte>^ buffer, int offset, int count) override {
        pin_ptr<unsigned char> p = &buffer [0];
        char* output = reinterpret_cast<char*>(p);

        if (!loaded) {
            int n = sizeof (WAVEFILEHEADER);
            memcpy (output + offset, (char*)header + position, n);
            count -= n;
            offset += n;
            loaded = true;
        }

        int pos = position;
        int nWrite = ov_read (vf, output + offset, count, 0, word, 1, &pos);
        if (nWrite == 0) {
            ov_clear (vf);
            vf = NULL;
        }
        position = pos;
        return nWrite;
    }

    virtual void Write (array<byte>^ buffer, int offset, int count) override {
        throw gcnew NotSupportedException;
    }

    virtual long long Seek (long long offset, SeekOrigin origin) override {
        throw gcnew NotSupportedException;
    }

    virtual void SetLength (long long length) override {
        throw gcnew NotSupportedException;
    }
};

void main (int argc, char* argv[]) {

    if (argc < 1) {
        printf ("usage: playogg.exe file(.ogg)\n");
        return;
    }
    OggStream stream (argv[1]);

    SoundPlayer^ player = gcnew SoundPlayer (%stream);
    player->PlayLooping ();

    // Hit any key
    Console::Read ();
}