c++windowsunicodeoggvorbis

How to use ov_open_callbacks to open an Ogg Vorbis file from a stream


The standard way to open an Ogg Vorbis file is to use ov_fopen or ov_open. However, neither function works on Windows if the file path contains non-ASCII characters.

In this case, it is possible to wrap an existing std::ifstream (or a different input stream) and open it via ov_open_callbacks. When I tried to do this, I found the documentation a bit sketchy regarding the exact semantics of the required wrapper functions.

How can I wrap an existing stream in order to pass it to ov_open_callbacks?


Solution

  • Here's a working sample that opens an .ogg file and prints out some basic information. All wrapper functions (except close, which isn't needed in this scenario) are implemented, so the resulting OggVorbis_File struct is fully seekable.

    #include <vorbis/vorbisfile.h>
    #include <iostream>
    #include <cassert>
    #include <fstream>
    #include <iomanip>
    #include <vector>
    
    size_t read(void* buffer, size_t elementSize, size_t elementCount, void* dataSource) {
        assert(elementSize == 1);
    
        std::ifstream& stream = *static_cast<std::ifstream*>(dataSource);
        stream.read(static_cast<char*>(buffer), elementCount);
        const std::streamsize bytesRead = stream.gcount();
        stream.clear(); // In case we read past EOF
        return static_cast<size_t>(bytesRead);
    }
    
    int seek(void* dataSource, ogg_int64_t offset, int origin) {
        static const std::vector<std::ios_base::seekdir> seekDirections{
            std::ios_base::beg, std::ios_base::cur, std::ios_base::end
        };
    
        std::ifstream& stream = *static_cast<std::ifstream*>(dataSource);
        stream.seekg(offset, seekDirections.at(origin));
        stream.clear(); // In case we seeked to EOF
        return 0;
    }
    
    long tell(void* dataSource) {
        std::ifstream& stream = *static_cast<std::ifstream*>(dataSource);
        const auto position = stream.tellg();
        assert(position >= 0);
        return static_cast<long>(position);
    }
    
    int main() {
        // Open file stream
        std::ifstream stream;
        stream.open("C:\\path\\to\\file.ogg", std::ios::binary);
        OggVorbis_File file;
        const ov_callbacks callbacks{read, seek, nullptr, tell};
        int result = ov_open_callbacks(&stream, &file, nullptr, 0, callbacks);
        if (result < 0) {
            std::cout << "Error opening file: " << result << std::endl;
            return 0;
        }
    
        // Read file info
        vorbis_info* vorbisInfo = ov_info(&file, -1);
        std::cout << "File info: " << vorbisInfo->rate << "Hz, "
            << vorbisInfo->channels << " channels" << std::endl;
    
        // Close file
        ov_clear(&file);
    }