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