1 // Copyright 2014-2017 the openage authors. See copying.md for legal info.
2
3 #include "opus_dynamic_loader.h"
4
5 #include <opusfile.h>
6
7 #include "error.h"
8 #include "../log/log.h"
9
10 namespace openage {
11 namespace audio {
12
13
OpusDynamicLoader(const util::Path & path)14 OpusDynamicLoader::OpusDynamicLoader(const util::Path &path)
15 :
16 DynamicLoader{path},
17 source{open_opus_file(path)} {
18
19 // read channels from the opus file
20 channels = op_channel_count(this->source.handle.get(), -1);
21
22 int64_t pcm_length = op_pcm_total(this->source.handle.get(), -1);
23 if (pcm_length < 0) {
24 throw audio::Error{
25 ERR << "Could not seek in "
26 << path << ": " << pcm_length};
27 }
28
29 length = static_cast<size_t>(pcm_length) * 2;
30 }
31
32
load_chunk(int16_t * chunk_buffer,size_t offset,size_t chunk_size)33 size_t OpusDynamicLoader::load_chunk(int16_t *chunk_buffer,
34 size_t offset,
35 size_t chunk_size) {
36
37 // if the requested offset is greater than the resource's length, there is
38 // no chunk left to load
39 if (offset > this->length) {
40 return 0;
41 }
42
43 // seek to the requested offset, the seek offset is given in samples
44 // while the requested offset is given in int16_t values, so the division
45 // by 2 is necessary
46 int64_t pcm_offset = static_cast<int64_t>(offset / 2);
47
48 int op_ret = op_pcm_seek(this->source.handle.get(), pcm_offset);
49 if (op_ret < 0) {
50 throw audio::Error{
51 ERR << "Could not seek in " << this->path << ": " << op_ret
52 };
53 }
54
55 // read a chunk from the requested offset
56 // if the opus file is a mono source, we read chunk_size / 2 values and
57 // convert it to stereo directly
58 // if the opus file is a stereo source, we read chunk_size directly
59 int read_num_values = chunk_size / 2 * channels;
60 int read_count = 0;
61
62 // loop as long as there are samples left to read
63 while (read_count <= read_num_values) {
64 int samples_read = op_read(
65 this->source.handle.get(),
66 chunk_buffer + read_count,
67 read_num_values - read_count,
68 nullptr
69 );
70
71 // an error occurred
72 if (samples_read < 0) {
73 throw audio::Error{
74 ERR << "Could not read from "
75 << this->path << ": " << samples_read
76 };
77 }
78 // end of the resource
79 else if (samples_read == 0) {
80 break;
81 }
82
83 // increase read_count by the number of int16_t values that have been
84 // read
85 read_count += samples_read * channels;
86 }
87
88 // convert to stereo
89 if (channels == 1) {
90 for (int i = read_count-1; i >= 0; i--) {
91 auto value = chunk_buffer[i];
92 chunk_buffer[i*2+1] = value;
93 chunk_buffer[i*2] = value;
94 }
95 }
96
97 return (read_count * 2) / channels;
98 }
99
100 }} // openage::audio
101