vorbis.cpp
1 /* 2 Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au> 3 Copyright (C) 2020 EDuke32 developers and contributors 4 5 This program is free software; you can redistribute it and/or 6 modify it under the terms of the GNU General Public License 7 as published by the Free Software Foundation; either version 2 8 of the License, or (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 14 See the GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, write to the Free Software 18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 20 */ 21 22 /** 23 * OggVorbis source support for MultiVoc 24 */ 25 26 #include "_multivc.h" 27 28 #include "baselayer.h" 29 30 #ifdef _WIN32 31 #include "winbits.h" 32 #endif 33 34 #ifdef HAVE_VORBIS 35 36 #define BLOCKSIZE 512 37 38 #define OGG_IMPL 39 #define VORBIS_IMPL 40 #define OV_EXCLUDE_STATIC_CALLBACKS 41 42 #include "minivorbis.h" 43 44 typedef struct { 45 void * ptr; 46 size_t length; 47 size_t pos; 48 49 OggVorbis_File vf; 50 51 char block[BLOCKSIZE]; 52 int lastbitstream; 53 } vorbis_data; 54 55 // designed with multiple calls in mind 56 static void MV_GetVorbisCommentLoops(VoiceNode *voice, vorbis_comment *vc) 57 { 58 const char *vc_loopstart = nullptr; 59 const char *vc_loopend = nullptr; 60 const char *vc_looplength = nullptr; 61 62 for (int comment = 0; comment < vc->comments; ++comment) 63 { 64 auto entry = (const char *)vc->user_comments[comment]; 65 if (entry != nullptr && entry[0] != '\0') 66 { 67 const char *value = Bstrchr(entry, '='); 68 69 if (!value) 70 continue; 71 72 const size_t field = value - entry; 73 value += 1; 74 75 for (int t = 0; t < loopStartTagCount && vc_loopstart == nullptr; ++t) 76 { 77 auto tag = loopStartTags[t]; 78 if (field == Bstrlen(tag) && Bstrncasecmp(entry, tag, field) == 0) 79 vc_loopstart = value; 80 } 81 82 for (int t = 0; t < loopEndTagCount && vc_loopend == nullptr; ++t) 83 { 84 auto tag = loopEndTags[t]; 85 if (field == Bstrlen(tag) && Bstrncasecmp(entry, tag, field) == 0) 86 vc_loopend = value; 87 } 88 89 for (int t = 0; t < loopLengthTagCount && vc_looplength == nullptr; ++t) 90 { 91 auto tag = loopLengthTags[t]; 92 if (field == Bstrlen(tag) && Bstrncasecmp(entry, tag, field) == 0) 93 vc_looplength = value; 94 } 95 } 96 } 97 98 auto vd = (vorbis_data *)voice->rawdataptr; 99 auto total = ov_pcm_total(&vd->vf, -1); 100 101 if (vc_loopstart != nullptr) 102 { 103 const ogg_int64_t ov_loopstart = Batol(vc_loopstart); 104 if ((unsigned)(ov_loopstart-1) <= total) 105 { 106 voice->Loop.Start = (const char *) (intptr_t) ov_loopstart; 107 voice->Loop.Size = 1; 108 } 109 else LOG_F(WARNING, "MV_GetVorbisCommentLoops: loop start is beyond end of data"); 110 } 111 if (vc_loopend != nullptr) 112 { 113 if (voice->Loop.Size > 0) 114 { 115 const ogg_int64_t ov_loopend = Batol(vc_loopend); 116 if ((unsigned)(ov_loopend-1) <= total) 117 voice->Loop.End = (const char *) (intptr_t) ov_loopend; 118 else LOG_F(WARNING, "MV_GetVorbisCommentLoops: loop end is beyond end of data"); 119 } 120 } 121 if (vc_looplength != nullptr) 122 { 123 if (voice->Loop.Size > 0 && voice->Loop.End == 0) 124 { 125 const ogg_int64_t ov_looplength = Batol(vc_looplength); 126 if (ov_looplength > 0) // a loop of length 0 is invalid 127 voice->Loop.End = (const char *) ((intptr_t) ov_looplength + (intptr_t) voice->Loop.Start); 128 else LOG_F(WARNING, "MV_GetVorbisCommentLoops: loop length is zero"); 129 } 130 } 131 } 132 133 // callbacks 134 135 static size_t read_vorbis(void *ptr, size_t size, size_t nmemb, void *datasource) 136 { 137 auto vorb = (vorbis_data *)datasource; 138 139 errno = 0; 140 141 if (vorb->length == vorb->pos) 142 return 0; 143 144 int nread = 0; 145 146 for (; nmemb > 0; nmemb--, nread++) 147 { 148 int bytes = vorb->length - vorb->pos; 149 150 if ((signed)size < bytes) 151 bytes = (int)size; 152 153 memcpy(ptr, (uint8_t *)vorb->ptr + vorb->pos, bytes); 154 vorb->pos += bytes; 155 ptr = (uint8_t *)ptr + bytes; 156 157 if (vorb->length == vorb->pos) 158 { 159 nread++; 160 break; 161 } 162 } 163 164 return nread; 165 } 166 167 168 static int seek_vorbis(void *datasource, ogg_int64_t offset, int whence) 169 { 170 auto vorb = (vorbis_data *)datasource; 171 172 switch (whence) 173 { 174 case SEEK_SET: vorb->pos = 0; break; 175 case SEEK_CUR: break; 176 case SEEK_END: vorb->pos = vorb->length; break; 177 } 178 179 vorb->pos += offset; 180 181 if (vorb->pos > vorb->length) 182 vorb->pos = vorb->length; 183 184 return vorb->pos; 185 } 186 187 static int close_vorbis(void *) { return 0; } 188 189 static long tell_vorbis(void *datasource) 190 { 191 auto vorb = (vorbis_data *)datasource; 192 193 return vorb->pos; 194 } 195 196 197 int MV_GetVorbisPosition(VoiceNode *voice) 198 { 199 auto vd = (vorbis_data *) voice->rawdataptr; 200 201 return ov_pcm_tell(&vd->vf); 202 } 203 204 void MV_SetVorbisPosition(VoiceNode *voice, int position) 205 { 206 auto vd = (vorbis_data *) voice->rawdataptr; 207 208 ov_pcm_seek(&vd->vf, position); 209 } 210 211 static playbackstatus MV_GetNextVorbisBlock(VoiceNode *voice) 212 { 213 int bitstream; 214 int bytesread = 0; 215 auto vd = (vorbis_data *)voice->rawdataptr; 216 217 if (!vd) 218 { 219 LOG_F(ERROR, "MV_GetNextVorbisBlock: bad rawdataptr!"); 220 return NoMoreData; 221 } 222 223 do 224 { 225 #ifdef USING_TREMOR 226 int bytes = ov_read(&vd->vf, vd->block + bytesread, BLOCKSIZE - bytesread, &bitstream); 227 #else 228 int bytes = ov_read(&vd->vf, vd->block + bytesread, BLOCKSIZE - bytesread, 0, 2, 1, &bitstream); 229 #endif 230 // fprintf(stderr, "ov_read = %d\n", bytes); 231 if (bytes > 0) 232 { 233 ogg_int64_t currentPosition; 234 bytesread += bytes; 235 if ((ogg_int64_t)(intptr_t)voice->Loop.End > 0 && 236 (currentPosition = ov_pcm_tell(&vd->vf)) >= (ogg_int64_t)(intptr_t)voice->Loop.End) 237 { 238 bytesread -= 239 (currentPosition - (ogg_int64_t)(intptr_t)voice->Loop.End) * voice->channels * 2; // (voice->bits>>3) 240 241 int const err = ov_pcm_seek(&vd->vf, (ogg_int64_t)(intptr_t)voice->Loop.Start); 242 243 if (err != 0) 244 { 245 LOG_F(ERROR, "MV_GetNextVorbisBlock: error %d in ov_pcm_seek (LOOP_START %" PRIi64 ", LOOP_END %" PRIi64 ")", 246 err, (ogg_int64_t)(intptr_t)voice->Loop.Start, (ogg_int64_t)(intptr_t)voice->Loop.End); 247 } 248 } 249 continue; 250 } 251 else if (bytes == OV_HOLE) 252 continue; 253 else if (bytes == 0) 254 { 255 if (voice->Loop.Size > 0) 256 { 257 int const err = ov_pcm_seek(&vd->vf, (ogg_int64_t)(intptr_t)voice->Loop.Start); 258 259 if (err != 0) 260 { 261 LOG_F(ERROR, "MV_GetNextVorbisBlock: error %d in ov_pcm_seek (LOOP_START %" PRIi64 ")", 262 err, (ogg_int64_t)(intptr_t)voice->Loop.Start); 263 } 264 else 265 continue; 266 } 267 else 268 { 269 break; 270 } 271 } 272 else if (bytes < 0) 273 { 274 LOG_F(ERROR, "MV_GetNextVorbisBlock: error %d in ov_read", bytes); 275 voice->rawdataptr = nullptr; 276 voice->rawdatasiz = 0; 277 ov_clear(&vd->vf); 278 ALIGNED_FREE_AND_NULL(vd); 279 return NoMoreData; 280 } 281 } while (bytesread < BLOCKSIZE); 282 283 if (bytesread == 0) 284 return NoMoreData; 285 286 if (bitstream != vd->lastbitstream) 287 { 288 vorbis_info *vi = ov_info(&vd->vf, -1); 289 if (!vi || (vi->channels != 1 && vi->channels != 2)) 290 return NoMoreData; 291 292 voice->channels = vi->channels; 293 voice->SamplingRate = vi->rate; 294 voice->RateScale = divideu64((uint64_t)voice->SamplingRate * voice->PitchScale, MV_MixRate); 295 296 voice->FixedPointBufferSize = (voice->RateScale * MV_MIXBUFFERSIZE) - voice->RateScale; 297 vd->lastbitstream = bitstream; 298 MV_SetVoiceMixMode(voice); 299 } 300 301 uint32_t const samples = divideu32(bytesread, ((voice->bits>>3) * voice->channels)); 302 303 voice->position = 0; 304 voice->sound = vd->block; 305 voice->length = samples << 16; 306 307 #ifdef GEKKO 308 // If libtremor had the three additional ov_read() parameters that libvorbis has, 309 // this would be better handled using the endianness parameter. 310 int16_t *data = (int16_t *)(vd->block); // assumes signed 16-bit 311 for (bytesread = 0; bytesread < BLOCKSIZE / 2; ++bytesread) 312 data[bytesread] = (data[bytesread] & 0xff) << 8 | ((data[bytesread] & 0xff00) >> 8); 313 #endif 314 315 return KeepPlaying; 316 } 317 318 int MV_PlayVorbis3D(char *ptr, uint32_t length, int loophow, int pitchoffset, int angle, int distance, int priority, fix16_t volume, intptr_t callbackval) 319 { 320 if (!MV_Installed) 321 return MV_SetErrorCode(MV_NotInstalled); 322 323 if (distance < 0) 324 { 325 distance = -distance; 326 angle += MV_NUMPANPOSITIONS / 2; 327 } 328 329 int const vol = MIX_VOLUME(distance); 330 331 // Ensure angle is within 0 - 127 332 angle &= MV_MAXPANPOSITION; 333 334 return MV_PlayVorbis(ptr, length, loophow, -1, pitchoffset, max(0, 255 - distance), 335 MV_PanTable[angle][vol].left, MV_PanTable[angle][vol].right, priority, volume, callbackval); 336 } 337 338 static constexpr ov_callbacks vorbis_callbacks = { read_vorbis, seek_vorbis, close_vorbis, tell_vorbis }; 339 340 int MV_PlayVorbis(char *ptr, uint32_t length, int loopstart, int loopend, int pitchoffset, int vol, int left, int right, int priority, fix16_t volume, intptr_t callbackval) 341 { 342 UNREFERENCED_PARAMETER(loopend); 343 344 if (!MV_Installed) 345 return MV_SetErrorCode(MV_NotInstalled); 346 347 auto voice = MV_AllocVoice(priority, sizeof(vorbis_data)); 348 if (voice == nullptr) 349 return MV_SetErrorCode(MV_NoVoices); 350 351 auto vd = (vorbis_data *)voice->rawdataptr; 352 353 vd->ptr = ptr; 354 vd->pos = 0; 355 vd->length = length; 356 357 // pitchoffset is passed into the worker tasks using the preexisting lastbitstream member 358 vd->lastbitstream = pitchoffset; 359 360 voice->wavetype = FMT_VORBIS; 361 voice->bits = 16; 362 voice->NextBlock = vd->block; 363 voice->priority = priority; 364 voice->callbackval = callbackval; 365 voice->Loop = { nullptr, nullptr, 0, (loopstart >= 0) }; 366 voice->GetSound = MV_GetNextVorbisBlock; 367 voice->Paused = true; 368 369 MV_SetVoiceMixMode(voice); 370 MV_SetVoiceVolume(voice, vol, left, right, volume); 371 372 //if (vd->task.valid() && !vd->task.ready()) 373 // vd->task.wait(); 374 375 voice->task = async::spawn([voice]() -> int 376 { 377 #if defined _WIN32 378 debugThreadName("MV_PlayVorbis"); 379 #endif 380 auto vd = (vorbis_data *)voice->rawdataptr; 381 382 // yoinked ptr indicates we're in some shitshow scenario where we tried to cancel 383 // the voice before the decoder even got a chance to start initializing 384 385 if (!vd) 386 { 387 voice->rawdatasiz = 0; 388 MV_PlayVoice(voice); 389 return MV_SetErrorCode(MV_VoiceNotFound); 390 } 391 392 int status = ov_open_callbacks((void *)vd, &vd->vf, 0, 0, vorbis_callbacks); 393 vorbis_info *vi = nullptr; 394 395 if (status < 0 || ((vi = ov_info(&vd->vf, 0)) == nullptr) || vi->channels < 1 || vi->channels > 2) 396 { 397 if (status == 0) 398 { 399 ov_clear(&vd->vf); 400 if (vi) LOG_F(ERROR, "MV_PlayVorbis: unsupported subformat: channels = %d", vi->channels); 401 else LOG_F(ERROR, "MV_PlayVorbis: bad ov_info"); 402 } 403 else 404 LOG_F(ERROR, "MV_PlayVorbis: error %d in ov_open_callbacks", status); 405 406 ALIGNED_FREE_AND_NULL(voice->rawdataptr); 407 voice->rawdatasiz = 0; 408 MV_PlayVoice(voice); 409 return MV_SetErrorCode(MV_InvalidFile); 410 } 411 412 voice->channels = vi->channels; 413 414 // load loop tags from metadata 415 if (auto comment = ov_comment(&vd->vf, 0)) 416 MV_GetVorbisCommentLoops(voice, comment); 417 418 MV_SetVoicePitch(voice, vi->rate, vd->lastbitstream); 419 vd->lastbitstream = -1; 420 MV_PlayVoice(voice); 421 return MV_Ok; 422 }); 423 424 return voice->handle; 425 } 426 427 void MV_ReleaseVorbisVoice(VoiceNode *voice) 428 { 429 Bassert(voice->wavetype == FMT_VORBIS && voice->rawdataptr != nullptr && voice->rawdatasiz == sizeof(vorbis_data)); 430 431 auto vd = (vorbis_data *)voice->rawdataptr; 432 //vd->task.wait(); 433 434 ov_clear(&vd->vf); 435 436 if (MV_LazyAlloc) 437 return; 438 439 voice->rawdataptr = nullptr; 440 voice->rawdatasiz = 0; 441 ALIGNED_FREE_AND_NULL(vd); 442 } 443 #else 444 #include "_multivc.h" 445 446 int MV_PlayVorbis(char *, uint32_t, int, int, int, int, int, int, int, fix16_t, intptr_t) 447 { 448 LOG_F(ERROR, "MV_PlayVorbis: OggVorbis support not included in this binary."); 449 return -1; 450 } 451 452 int MV_PlayVorbis3D(char *, uint32_t, int, int, int, int, int, fix16_t, intptr_t) 453 { 454 LOG_F(ERROR, "MV_PlayVorbis: OggVorbis support not included in this binary."); 455 return -1; 456 } 457 #endif //HAVE_VORBIS