1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004-2021 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 // * Redistributions of source code must retain the above copyright notice,
11 // this list of conditions and the following disclaimer.
12 //
13 // * Redistributions in binary form must reproduce the above copyright
14 // notice, this list of conditions and the following disclaimer in the
15 // documentation and/or other materials provided with the distribution.
16 //
17 // * Neither the name of the author nor the names of other contributors may
18 // be used to endorse or promote products derived from this software
19 // without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34
35 #include "Transcoder.h"
36 #include "BlockingTranscoder.h"
37 #include "TranscodingAudioDataStream.h"
38 #include "Constants.h"
39 #include "Util.h"
40 #include <musikcore/sdk/IBlockingEncoder.h>
41
42 #include <thread>
43 #include <set>
44
45 #pragma warning(push, 0)
46 #include <boost/filesystem.hpp>
47 #pragma warning(pop)
48
49 using namespace musik::core::sdk;
50 using namespace boost::filesystem;
51
52 std::mutex transcoderMutex;
53 std::condition_variable waitForTranscode;
54 std::set<std::string> runningBlockingTranscoders;
55
getEncoder(Context & context,const std::string & format)56 static IEncoder* getEncoder(Context& context, const std::string& format) {
57 std::string extension = "." + format;
58 return context.environment->GetEncoder(extension.c_str());
59 }
60
61 template <typename T>
getTypedEncoder(Context & context,const std::string & format)62 static T* getTypedEncoder(Context& context, const std::string& format) {
63 IEncoder* encoder = getEncoder(context, format);
64 if (encoder) {
65 T* typedEncoder = dynamic_cast<T*>(encoder);
66 if (typedEncoder) {
67 return typedEncoder;
68 }
69 encoder->Release();
70 }
71 return nullptr;
72 }
73
cachePath(Context & context)74 static std::string cachePath(Context& context) {
75 char buf[4096];
76 context.environment->GetPath(PathType::Data, buf, sizeof(buf));
77 std::string path = std::string(buf) + "/cache/transcoder/";
78 boost::filesystem::path boostPath(path);
79 if (!exists(boostPath)) {
80 create_directories(boostPath);
81 }
82
83 return path;
84 }
85
iterateTranscodeCache(Context & context,std::function<void (path)> cb)86 static void iterateTranscodeCache(Context& context, std::function<void(path)> cb) {
87 if (cb) {
88 directory_iterator end;
89 directory_iterator file(cachePath(context));
90
91 while (file != end) {
92 if (!is_directory(file->status())) {
93 cb(file->path());
94 }
95 ++file;
96 }
97 }
98 }
99
RemoveTempTranscodeFiles(Context & context)100 void Transcoder::RemoveTempTranscodeFiles(Context& context) {
101 iterateTranscodeCache(context, [](path p) {
102 if (p.extension().string() == ".tmp") {
103 boost::system::error_code ec;
104 remove(p, ec);
105 }
106 });
107 }
108
PruneTranscodeCache(Context & context)109 void Transcoder::PruneTranscodeCache(Context& context) {
110 std::map<time_t, path> sorted;
111
112 boost::system::error_code ec;
113 iterateTranscodeCache(context, [&sorted, &ec](path p) {
114 sorted[last_write_time(p, ec)] = p;
115 });
116
117 int maxSize = context.prefs->GetInt(
118 prefs::transcoder_cache_count.c_str(),
119 defaults::transcoder_cache_count);
120
121 int extra = (int) sorted.size() - (maxSize - 1);
122 auto it = sorted.begin();
123 while (extra > 0 && it != sorted.end()) {
124 auto p = it->second;
125 boost::system::error_code ec;
126 if (remove(p, ec)) {
127 --extra;
128 }
129 ++it;
130 }
131 }
132
getTempAndFinalFilename(Context & context,const std::string & uri,size_t bitrate,const std::string & format,std::string & tempFn,std::string & finalFn)133 static void getTempAndFinalFilename(
134 Context& context,
135 const std::string& uri,
136 size_t bitrate,
137 const std::string& format,
138 std::string& tempFn,
139 std::string& finalFn)
140 {
141 finalFn = std::string(
142 cachePath(context) +
143 std::to_string(std::hash<std::string>()(uri)) +
144 "-" + std::to_string(bitrate) +
145 "." + format);
146
147 do {
148 tempFn = finalFn + "." + std::to_string(rand()) + ".tmp";
149 } while (exists(tempFn));
150 }
151
Transcode(Context & context,const std::string & uri,size_t bitrate,const std::string & format)152 IDataStream* Transcoder::Transcode(
153 Context& context,
154 const std::string& uri,
155 size_t bitrate,
156 const std::string& format)
157 {
158 if (context.prefs->GetBool(
159 prefs::transcoder_synchronous.c_str(),
160 defaults::transcoder_synchronous))
161 {
162 return TranscodeAndWait(context, getEncoder(context, format), uri, bitrate, format);
163 }
164
165 /* on-demand is the default. however, on-demand transcoding is only available
166 for `IStreamingEncoder` types. */
167 IStreamingEncoder* audioStreamEncoder = getTypedEncoder<IStreamingEncoder>(context, format);
168 if (audioStreamEncoder) {
169 return TranscodeOnDemand(context, audioStreamEncoder, uri, bitrate, format);
170 }
171
172 return TranscodeAndWait(context, nullptr, uri, bitrate, format);
173 }
174
TranscodeOnDemand(Context & context,IStreamingEncoder * encoder,const std::string & uri,size_t bitrate,const std::string & format)175 IDataStream* Transcoder::TranscodeOnDemand(
176 Context& context,
177 IStreamingEncoder* encoder,
178 const std::string& uri,
179 size_t bitrate,
180 const std::string& format)
181 {
182 /* the caller can specify an encoder; if it is not specified, go ahead and
183 create one here */
184 if (!encoder) {
185 encoder = getTypedEncoder<IStreamingEncoder>(context, format);
186 if (!encoder) {
187 return nullptr;
188 }
189 }
190
191 /* see if it already exists in the cache. if it does, just return it. */
192 std::string expectedFilename, tempFilename;
193 getTempAndFinalFilename(context, uri, bitrate, format, tempFilename, expectedFilename);
194
195 if (exists(expectedFilename)) {
196 boost::system::error_code ec;
197 last_write_time(expectedFilename, time(nullptr), ec);
198 return context.environment->GetDataStream(expectedFilename.c_str(), OpenFlags::Read);
199 }
200
201 /* if it doesn't exist, check to see if the cache is enabled. */
202 int cacheCount = context.prefs->GetInt(
203 prefs::transcoder_cache_count.c_str(),
204 defaults::transcoder_cache_count);
205
206 TranscodingAudioDataStream* transcoderStream = nullptr;
207
208 if (cacheCount > 0) {
209 PruneTranscodeCache(context);
210
211 transcoderStream = new TranscodingAudioDataStream(
212 context, encoder, uri, tempFilename, expectedFilename, bitrate, format);
213
214 /* if the stream has an indeterminite length, close it down and
215 re-open it without caching options; we don't want to fill up
216 the storage disk */
217 if (transcoderStream->Length() < 0) {
218 transcoderStream->Release();
219 delete transcoderStream;
220 transcoderStream = new TranscodingAudioDataStream(context, encoder, uri, bitrate, format);
221 }
222 }
223 else {
224 transcoderStream = new TranscodingAudioDataStream(context, encoder, uri, bitrate, format);
225 }
226
227 return transcoderStream;
228 }
229
TranscodeAndWait(Context & context,IEncoder * encoder,const std::string & uri,size_t bitrate,const std::string & format)230 IDataStream* Transcoder::TranscodeAndWait(
231 Context& context,
232 IEncoder* encoder,
233 const std::string& uri,
234 size_t bitrate,
235 const std::string& format)
236 {
237 /* the caller can specify an encoder; if it is not specified, go ahead and
238 create one here */
239 if (!encoder) {
240 encoder = getEncoder(context, format);
241 if (!encoder) {
242 return nullptr;
243 }
244 }
245
246 std::string expectedFilename, tempFilename;
247 getTempAndFinalFilename(context, uri, bitrate, format, tempFilename, expectedFilename);
248
249 /* already exists? */
250 if (exists(expectedFilename)) {
251 boost::system::error_code ec;
252 last_write_time(expectedFilename, time(nullptr), ec);
253 return context.environment->GetDataStream(expectedFilename.c_str(), OpenFlags::Read);
254 }
255
256 IStreamingEncoder* audioStreamEncoder = dynamic_cast<IStreamingEncoder*>(encoder);
257 if (audioStreamEncoder) {
258 TranscodingAudioDataStream* transcoderStream = new TranscodingAudioDataStream(
259 context, audioStreamEncoder, uri, tempFilename, expectedFilename, bitrate, format);
260
261 /* transcoders with a negative length have an indeterminate duration, so
262 we disallow waiting for them because they may never finish */
263 if (transcoderStream->Length() < 0) {
264 transcoderStream->Release();
265 delete transcoderStream;
266 return nullptr;
267 }
268
269 char buffer[8192];
270 while (!transcoderStream->Eof()) {
271 transcoderStream->Read(buffer, sizeof(buffer));
272 std::this_thread::yield();
273 }
274
275 transcoderStream->Release();
276 PruneTranscodeCache(context);
277 return context.environment->GetDataStream(uri.c_str(), OpenFlags::Read);
278 }
279 else {
280 IBlockingEncoder* blockingEncoder = dynamic_cast<IBlockingEncoder*>(encoder);
281 if (blockingEncoder) {
282 bool alreadyTranscoding = false;
283 {
284 /* see if there's already a blocking transcoder running for the specified
285 uri. if there is, wait for it to complete. if there's not, add it to the
286 running set */
287 std::unique_lock<std::mutex> lock(transcoderMutex);
288 alreadyTranscoding = runningBlockingTranscoders.find(uri) != runningBlockingTranscoders.end();
289 if (alreadyTranscoding) {
290 while (runningBlockingTranscoders.find(uri) != runningBlockingTranscoders.end()) {
291 waitForTranscode.wait(lock);
292 }
293 }
294 else {
295 runningBlockingTranscoders.insert(uri);
296 }
297 }
298
299 if (!alreadyTranscoding) {
300 BlockingTranscoder blockingTranscoder(
301 context, blockingEncoder, uri, tempFilename, expectedFilename, bitrate);
302
303 bool success = blockingTranscoder.Transcode();
304
305 {
306 /* let anyone else waiting for a resource to be transcoding that we
307 finished. */
308 std::unique_lock<std::mutex> lock(transcoderMutex);
309 auto it = runningBlockingTranscoders.find(uri);
310 if (it != runningBlockingTranscoders.end()) {
311 runningBlockingTranscoders.erase(it);
312 }
313 waitForTranscode.notify_all();
314 }
315
316 if (!success) {
317 return nullptr;
318 }
319 }
320 }
321
322 PruneTranscodeCache(context);
323 return context.environment->GetDataStream(expectedFilename.c_str(), OpenFlags::Read);
324 }
325 }
326
GetActiveCount()327 int Transcoder::GetActiveCount() {
328 return BlockingTranscoder::GetActiveCount() + TranscodingAudioDataStream::GetActiveCount();
329 }