1 /*
2  Copyright (c) 2013 yvt
3 
4  This file is part of OpenSpades.
5 
6  OpenSpades is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  OpenSpades is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with OpenSpades.  If not, see <http://www.gnu.org/licenses/>.
18 
19  */
20 
21 #include <cstring>
22 #include <string>
23 
24 #include <opusfile.h>
25 
26 #include "OpusAudioStream.h"
27 
28 #include "Debug.h"
29 #include "Exception.h"
30 #include <Core/Strings.h>
31 
32 namespace spades {
33 
34 	namespace {
stringifyOpusErrorCode(int error)35 		std::string stringifyOpusErrorCode(int error) {
36 			const char *errorMessage = nullptr;
37 			switch (error) {
38 				case OP_EREAD: errorMessage = "OP_EREAD"; break;
39 				case OP_EFAULT: errorMessage = "OP_EFAULT"; break;
40 				case OP_EIMPL: errorMessage = "OP_EIMPL"; break;
41 				case OP_EINVAL: errorMessage = "OP_EINVAL"; break;
42 				case OP_ENOTFORMAT: errorMessage = "OP_ENOTFORMAT"; break;
43 				case OP_EBADHEADER: errorMessage = "OP_EBADHEADER"; break;
44 				case OP_EVERSION: errorMessage = "OP_EVERSION"; break;
45 				case OP_EBADLINK: errorMessage = "OP_EBADLINK"; break;
46 				case OP_EBADTIMESTAMP: errorMessage = "OP_EBADTIMESTAMP"; break;
47 			}
48 			if (errorMessage) {
49 				return errorMessage;
50 			} else {
51 				return Format("{0}", error);
52 			}
53 		}
54 	}
55 
OpusAudioStream(IStream * s,bool ac)56 	OpusAudioStream::OpusAudioStream(IStream *s, bool ac) : subsamplePosition{0} {
57 		SPADES_MARK_FUNCTION();
58 
59 		stream = s;
60 		autoClose = ac;
61 
62 		opusCallback.reset(new OpusFileCallbacks());
__anonc2605ac60202(void *stream, unsigned char *ptr, int nbytes) 63 		opusCallback->read = [](void *stream, unsigned char *ptr, int nbytes) -> int {
64 			auto &self = *reinterpret_cast<OpusAudioStream *>(stream);
65 			return static_cast<int>(self.stream->Read(ptr, static_cast<int>(nbytes)));
66 		};
__anonc2605ac60302(void *stream, opus_int64 offset, int whence) 67 		opusCallback->seek = [](void *stream, opus_int64 offset, int whence) -> int {
68 			auto &self = *reinterpret_cast<OpusAudioStream *>(stream);
69 			switch (whence) {
70 				case SEEK_CUR:
71 					self.stream->SetPosition(
72 					  static_cast<std::uint64_t>(offset + self.stream->GetPosition()));
73 					break;
74 				case SEEK_SET: self.stream->SetPosition(static_cast<std::uint64_t>(offset)); break;
75 				case SEEK_END:
76 					self.stream->SetPosition(
77 					  static_cast<std::uint64_t>(offset + self.stream->GetLength()));
78 					break;
79 			}
80 			return 0;
81 		};
__anonc2605ac60402(void *stream) 82 		opusCallback->tell = [](void *stream) -> opus_int64 {
83 			auto &self = *reinterpret_cast<OpusAudioStream *>(stream);
84 			return static_cast<opus_int64>(self.stream->GetPosition());
85 		};
86 		opusCallback->close = nullptr;
87 
88 		int result = 0;
89 		opusFile = op_open_callbacks(this, opusCallback.get(), nullptr, 0, &result);
90 
91 		if (result) {
92 			SPRaise("op_open_callbacks failed with error code %s",
93 			        stringifyOpusErrorCode(result).c_str());
94 		}
95 
96 		channels = op_channel_count(opusFile, 0);
97 		currentSample.resize(channels);
98 
99 		SetPosition(0);
100 	}
101 
~OpusAudioStream()102 	OpusAudioStream::~OpusAudioStream() {
103 		SPADES_MARK_FUNCTION();
104 
105 		op_free(opusFile);
106 
107 		if (autoClose)
108 			delete stream;
109 	}
110 
GetLength()111 	uint64_t OpusAudioStream::GetLength() { return op_pcm_total(opusFile, 0) * channels * 4; }
112 
GetSamplingFrequency()113 	int OpusAudioStream::GetSamplingFrequency() { return 48000; }
114 
GetNumChannels()115 	int OpusAudioStream::GetNumChannels() { return channels; }
116 
GetSampleFormat()117 	OpusAudioStream::SampleFormat OpusAudioStream::GetSampleFormat() {
118 		return SampleFormat::SingleFloat;
119 	}
120 
ReadByte()121 	int OpusAudioStream::ReadByte() {
122 		SPADES_MARK_FUNCTION();
123 
124 		if (subsamplePosition == 0) {
125 			if (op_pcm_tell(opusFile) >= op_pcm_total(opusFile, 0)) {
126 				return -1;
127 			}
128 			int result = op_read_float(opusFile, currentSample.data(), channels, nullptr);
129 			if (result < 0) {
130 				SPRaise("op_read_float failed with error code %s",
131 				        stringifyOpusErrorCode(result).c_str());
132 			} else if (result != 1) {
133 				SPRaise("op_read_float returned %d (expected: 1)", result);
134 			}
135 		}
136 
137 		int ret = reinterpret_cast<const uint8_t *>(currentSample.data())[subsamplePosition];
138 
139 		if ((++subsamplePosition) == channels * 4) {
140 			subsamplePosition = 0;
141 		}
142 
143 		return ret;
144 	}
145 
Read(void * data,size_t bytes)146 	size_t OpusAudioStream::Read(void *data, size_t bytes) {
147 		SPADES_MARK_FUNCTION();
148 
149 		uint64_t maxLen = GetLength() - GetPosition();
150 		if ((uint64_t)bytes > maxLen)
151 			bytes = (size_t)maxLen;
152 
153 		uint8_t *retBytes = reinterpret_cast<uint8_t *>(data);
154 		uint64_t remainingBytes = bytes;
155 
156 		if (subsamplePosition) {
157 			uint64_t copied =
158 			  std::min(remainingBytes, static_cast<uint64_t>(channels * 4 - subsamplePosition));
159 			std::memcpy(retBytes,
160 			            reinterpret_cast<uint8_t *>(currentSample.data()) + subsamplePosition,
161 			            static_cast<size_t>(copied));
162 			subsamplePosition += static_cast<int>(copied);
163 			remainingBytes -= copied;
164 			retBytes += copied;
165 
166 			if (subsamplePosition == channels * 4) {
167 				subsamplePosition = 0;
168 			}
169 		}
170 
171 		while (remainingBytes >= channels * 4) {
172 			uint64_t numSamples = remainingBytes / (channels * 4);
173 
174 			// 64-bit sample count might doesn't fit in int which op_read_float's third parameter
175 			// accepts
176 			numSamples = std::min<uint64_t>(numSamples, 0x1000000);
177 
178 			int result = op_read_float(opusFile, reinterpret_cast<float *>(retBytes),
179 			                           static_cast<int>(numSamples * channels), nullptr);
180 
181 			if (result < 0) {
182 				SPRaise("op_read_float failed with error code %s",
183 				        stringifyOpusErrorCode(result).c_str());
184 			} else if (result == 0) {
185 				SPRaise("op_read_float returned 0 (expected: > 0)");
186 			}
187 
188 			numSamples = static_cast<uint64_t>(result);
189 
190 			retBytes += numSamples * (channels * 4);
191 			remainingBytes -= numSamples * (channels * 4);
192 		}
193 
194 		if (remainingBytes) {
195 			int result = op_read_float(opusFile, currentSample.data(), channels, nullptr);
196 			if (result < 0) {
197 				SPRaise("op_read_float failed with error code %s",
198 				        stringifyOpusErrorCode(result).c_str());
199 			} else if (result != 1) {
200 				SPRaise("op_read_float returned %d (expected: 1)", result);
201 			}
202 
203 			std::memcpy(retBytes, reinterpret_cast<const uint8_t *>(currentSample.data()),
204 			            static_cast<size_t>(remainingBytes));
205 
206 			subsamplePosition = static_cast<int>(remainingBytes);
207 		}
208 
209 		return bytes;
210 	}
211 
GetPosition()212 	uint64_t OpusAudioStream::GetPosition() {
213 		if (subsamplePosition) {
214 			return (op_pcm_tell(opusFile) - 1) * channels * 4 + subsamplePosition;
215 		} else {
216 			return op_pcm_tell(opusFile) * channels * 4;
217 		}
218 	}
219 
SetPosition(uint64_t pos)220 	void OpusAudioStream::SetPosition(uint64_t pos) {
221 		pos = std::min(pos, GetLength());
222 
223 		op_pcm_seek(opusFile, static_cast<opus_int64>(pos / (channels * 4)));
224 
225 		subsamplePosition = static_cast<int>(pos % (channels * 4));
226 		if (subsamplePosition) {
227 			int result = op_read_float(opusFile, currentSample.data(), channels, nullptr);
228 			if (result < 0) {
229 				SPRaise("op_read_float failed with error code %s",
230 				        stringifyOpusErrorCode(result).c_str());
231 			} else if (result != 1) {
232 				SPRaise("op_read_float returned %d (expected: 1)", result);
233 			}
234 		}
235 	}
236 }
237