tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_FFmpegEncoderAudioFormat.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14#if TRACKTION_ENABLE_FFMPEG
15
16static juce::String readOutputFromSystem (juce::String cmd)
17{
18 juce::TemporaryFile tmpFile;
19 juce::TemporaryFile batFile (".bat");
20
21 #if JUCE_WINDOWS
22 STARTUPINFO si;
23 PROCESS_INFORMATION pi;
24
25 ZeroMemory (&si, sizeof (si));
26 si.cb = sizeof (si);
27 ZeroMemory (&pi, sizeof (pi));
28
29 cmd = cmd + " > " + tmpFile.getFile().getFullPathName().quoted();
30
31 if (cmd.length() > 32768)
32 {
34 return {};
35 }
36
37 batFile.getFile ().replaceWithText (cmd);
38
39 juce::String params = "/c " + batFile.getFile().getFullPathName().quoted();
40
41 juce::HeapBlock<char> commandLine (params.getNumBytesAsUTF8() + 1);
42 strcpy (commandLine.get(), params.toRawUTF8());
43
45 jassert (cmdExe.existsAsFile());
46
47 if (! CreateProcessA (cmdExe.getFullPathName().toRawUTF8(), commandLine.get(), nullptr, nullptr,
48 false, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi))
49 return {};
50
51 WaitForSingleObject (pi.hProcess, INFINITE);
52
53 CloseHandle (pi.hProcess);
54 CloseHandle (pi.hThread);
55 #else
56 cmd << " > " << tmpFile.getFile().getFullPathName().quoted();
57 auto result = std::system (cmd.toRawUTF8());
58 juce::ignoreUnused (result);
59 #endif
60
61 auto contents = tmpFile.getFile().loadFileAsString();
62 return contents;
63}
64
65class FFmpegEncoderAudioFormat::Writer : public juce::AudioFormatWriter
66{
67public:
68 Writer (juce::OutputStream* destStream, const juce::String& formatName,
69 const juce::File& exe, int vbr, int cbr,
70 double sampleRateIn, unsigned int numberOfChannels,
71 int bitsPerSampleIn, const juce::StringPairArray& md)
72 : AudioFormatWriter (destStream, formatName, sampleRateIn,
73 numberOfChannels, (unsigned int) bitsPerSampleIn),
74 vbrLevel (vbr), cbrBitrate (cbr), ffmpeg (exe), metadata (md)
75 {
76 if (auto out = tempWav.getFile().createOutputStream())
77 writer.reset (juce::WavAudioFormat().createWriterFor (out.release(), sampleRateIn, numChannels, bitsPerSampleIn, metadata, 0));
78 }
79
80 ~Writer() override
81 {
82 if (writer != nullptr)
83 {
84 writer = nullptr;
85
86 if (! convertToMP3())
87 convertToMP3();
88 }
89 }
90
91 bool write (const int** samplesToWrite, int numSamples) override
92 {
93 return writer != nullptr && writer->write (samplesToWrite, numSamples);
94 }
95
96private:
97 int vbrLevel, cbrBitrate;
98 juce::TemporaryFile tempWav { ".wav" };
100
101 bool runFFmpegChildProcess (const juce::TemporaryFile& tempMP3, const juce::StringArray& processArgs) const
102 {
104
105 DBG(processArgs.joinIntoString (" "));
106
107 [[maybe_unused]] auto output = readOutputFromSystem (processArgs.joinIntoString (" "));
108 DBG(output);
109
110 return tempMP3.getFile().getSize() > 0;
111 }
112
113 bool convertToMP3() const
114 {
115 juce::TemporaryFile tempMP3 (".mp3");
116
118
119 args.add (ffmpeg.getFullPathName().quoted());
120 args.add ("-i");
121 args.add (tempWav.getFile().getFullPathName().quoted());
122 args.add ("-codec:a");
123 args.add ("libmp3lame");
124
125 if (cbrBitrate == 0)
126 {
127 args.add ("-q:a");
128 args.add (juce::String (vbrLevel));
129 }
130 else
131 {
132 args.add ("-b:a");
133 args.add (juce::String::formatted ("%dk", cbrBitrate));
134 }
135
136 addMetadataArg (args, "id3title", "title");
137 addMetadataArg (args, "id3artist", "artist");
138 addMetadataArg (args, "id3album", "album");
139 addMetadataArg (args, "id3comment", "comment");
140 addMetadataArg (args, "id3date", "date");
141 addMetadataArg (args, "id3genre", "genre");
142 addMetadataArg (args, "id3trackNumber", "track");
143
144 args.add (tempMP3.getFile().getFullPathName().quoted());
145
146 if (runFFmpegChildProcess (tempMP3, args))
147 {
148 juce::FileInputStream fis (tempMP3.getFile());
149
150 if (fis.openedOk() && output->writeFromInputStream (fis, -1) > 0)
151 {
152 output->flush();
153 return true;
154 }
155 }
156
157 return false;
158 }
159
160 void addMetadataArg (juce::StringArray& args, const char* key, const char* ffmpegFlag) const
161 {
162 auto value = metadata.getValue (key, {});
163
164 if (value.isNotEmpty())
165 {
166 args.add ("-metadata");
167 args.add (juce::String (ffmpegFlag) + "=" + value.quoted());
168 }
169 }
170
171 const juce::File ffmpeg;
172 const juce::StringPairArray metadata;
173
175};
176
177//==============================================================================
178FFmpegEncoderAudioFormat::FFmpegEncoderAudioFormat (const juce::File& ffmpeg)
179 : AudioFormat ("MP3 file", ".mp3"), ffmpegExe (ffmpeg)
180{
181}
182
183FFmpegEncoderAudioFormat::~FFmpegEncoderAudioFormat()
184{
185}
186
187bool FFmpegEncoderAudioFormat::canHandleFile (const juce::File&)
188{
189 return false;
190}
191
192juce::Array<int> FFmpegEncoderAudioFormat::getPossibleSampleRates()
193{
194 return { 32000, 44100, 48000 };
195}
196
197juce::Array<int> FFmpegEncoderAudioFormat::getPossibleBitDepths()
198{
199 return { 16 };
200}
201
202bool FFmpegEncoderAudioFormat::canDoStereo() { return true; }
203bool FFmpegEncoderAudioFormat::canDoMono() { return true; }
204bool FFmpegEncoderAudioFormat::isCompressed() { return true; }
205
206juce::StringArray FFmpegEncoderAudioFormat::getQualityOptions()
207{
208 static const char* vbrOptions[] = { "VBR quality 0 (best)", "VBR quality 1", "VBR quality 2", "VBR quality 3",
209 "VBR quality 4 (normal)", "VBR quality 5", "VBR quality 6", "VBR quality 7",
210 "VBR quality 8", "VBR quality 9 (smallest)", nullptr };
211
212 juce::StringArray opts (vbrOptions);
213
214 const int cbrRates[] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 };
215
216 for (int i = 0; i < std::ssize (cbrRates); ++i)
217 opts.add (juce::String (cbrRates[i]) + " Kb/s CBR");
218
219 return opts;
220}
221
222juce::AudioFormatReader* FFmpegEncoderAudioFormat::createReaderFor (juce::InputStream*, const bool)
223{
224 return nullptr;
225}
226
227juce::AudioFormatWriter* FFmpegEncoderAudioFormat::createWriterFor (juce::OutputStream* streamToWriteTo,
228 double sampleRateToUse,
229 unsigned int numberOfChannels,
230 int bitsPerSample,
231 const juce::StringPairArray& metadataValues,
232 int qualityOptionIndex)
233{
234 if (streamToWriteTo == nullptr)
235 return nullptr;
236
237 int vbr = 4;
238 int cbr = 0;
239
240 const juce::String qual (getQualityOptions() [qualityOptionIndex]);
241
242 if (qual.contains ("VBR"))
243 vbr = qual.retainCharacters ("0123456789").getIntValue();
244 else
245 cbr = qual.getIntValue();
246
247 return new Writer (streamToWriteTo, getFormatName(), ffmpegExe, vbr, cbr, sampleRateToUse, numberOfChannels, bitsPerSample, metadataValues);
248}
249
250#endif
251
252}} // namespace tracktion { inline namespace engine
bool existsAsFile() const
int64 getSize() const
const String & getFullPathName() const noexcept
File getChildFile(StringRef relativeOrAbsolutePath) const
static File JUCE_CALLTYPE getSpecialLocation(const SpecialLocationType type)
String loadFileAsString() const
String joinIntoString(StringRef separatorString, int startIndex=0, int numberOfElements=-1) const
void add(String stringToAdd)
int length() const noexcept
const char * toRawUTF8() const
String quoted(juce_wchar quoteCharacter='"') const
size_t getNumBytesAsUTF8() const noexcept
static String formatted(const String &formatStr, Args... args)
const File & getFile() const noexcept
#define jassert(expression)
#define DBG(textToWrite)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
typedef int
void ignoreUnused(Types &&...) noexcept
write
T ssize(T... args)
strcpy
T system(T... args)