1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "BsOggVorbisDecoder.h"
4#include "FileSystem/BsDataStream.h"
5#include <vorbis/codec.h>
6
7namespace bs
8{
9 size_t oggRead(void* ptr, size_t size, size_t nmemb, void* data)
10 {
11 OggDecoderData* decoderData = static_cast<OggDecoderData*>(data);
12 return static_cast<std::size_t>(decoderData->stream->read(ptr, size * nmemb));
13 }
14
15 int oggSeek(void* data, ogg_int64_t offset, int whence)
16 {
17 OggDecoderData* decoderData = static_cast<OggDecoderData*>(data);
18 switch (whence)
19 {
20 case SEEK_SET:
21 offset += decoderData->offset;
22 break;
23 case SEEK_CUR:
24 offset += decoderData->stream->tell();
25 break;
26 case SEEK_END:
27 offset = std::max(0, (INT32)decoderData->stream->size() - 1);
28 break;
29 }
30
31 decoderData->stream->seek((UINT32)offset);
32 return (int)(decoderData->stream->tell() - decoderData->offset);
33 }
34
35 long oggTell(void* data)
36 {
37 OggDecoderData* decoderData = static_cast<OggDecoderData*>(data);
38 return (long)(decoderData->stream->tell() - decoderData->offset);
39 }
40
41 static ov_callbacks callbacks = { &oggRead, &oggSeek, nullptr, &oggTell };
42
43 OggVorbisDecoder::OggVorbisDecoder()
44 {
45 mOggVorbisFile.datasource = nullptr;
46 }
47
48 OggVorbisDecoder::~OggVorbisDecoder()
49 {
50 if (mOggVorbisFile.datasource != nullptr)
51 ov_clear(&mOggVorbisFile);
52 }
53
54 bool OggVorbisDecoder::isValid(const SPtr<DataStream>& stream, UINT32 offset)
55 {
56 stream->seek(offset);
57 mDecoderData.stream = stream;
58 mDecoderData.offset = offset;
59
60 OggVorbis_File file;
61 if (ov_test_callbacks(&mDecoderData, &file, nullptr, 0, callbacks) == 0)
62 {
63 ov_clear(&file);
64 return true;
65 }
66
67 return false;
68 }
69
70 bool OggVorbisDecoder::open(const SPtr<DataStream>& stream, AudioDataInfo& info, UINT32 offset)
71 {
72 if (stream == nullptr)
73 return false;
74
75 stream->seek(offset);
76 mDecoderData.stream = stream;
77 mDecoderData.offset = offset;
78
79 int status = ov_open_callbacks(&mDecoderData, &mOggVorbisFile, nullptr, 0, callbacks);
80 if (status < 0)
81 {
82 LOGERR("Failed to open Ogg Vorbis file.");
83 return false;
84 }
85
86 vorbis_info* vorbisInfo = ov_info(&mOggVorbisFile, -1);
87 info.numChannels = vorbisInfo->channels;
88 info.sampleRate = vorbisInfo->rate;
89 info.numSamples = (UINT32)(ov_pcm_total(&mOggVorbisFile, -1) * vorbisInfo->channels);
90 info.bitDepth = 16;
91
92 mChannelCount = info.numChannels;
93 return true;
94 }
95
96 void OggVorbisDecoder::seek(UINT32 offset)
97 {
98 ov_pcm_seek(&mOggVorbisFile, offset / mChannelCount);
99 }
100
101 UINT32 OggVorbisDecoder::read(UINT8* samples, UINT32 numSamples)
102 {
103 UINT32 numReadSamples = 0;
104 while (numReadSamples < numSamples)
105 {
106 INT32 bytesToRead = (INT32)(numSamples - numReadSamples) * sizeof(INT16);
107 UINT32 bytesRead = ov_read(&mOggVorbisFile, (char*)samples, bytesToRead, 0, 2, 1, nullptr);
108 if (bytesRead > 0)
109 {
110 UINT32 samplesRead = bytesRead / sizeof(INT16);
111 numReadSamples += samplesRead;
112 samples += samplesRead * sizeof(INT16);
113 }
114 else
115 break;
116 }
117
118 return numReadSamples;
119 }
120}
121