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 }