1 /*****************************************************************************
2 * Copyright (C) 2013-2020 MulticoreWare, Inc
3 *
4 * Authors: Steve Borho <steve@borho.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
19 *
20 * This program is also available under a commercial proprietary license.
21 * For more information, contact us at license @ x265.com.
22 *****************************************************************************/
23
24 #if _MSC_VER
25 #pragma warning(disable: 4127) // conditional expression is constant, yes I know
26 #endif
27
28 #include "x265.h"
29 #include "x265cli.h"
30 #include "abrEncApp.h"
31
32 #if HAVE_VLD
33 /* Visual Leak Detector */
34 #include <vld.h>
35 #endif
36
37 #include <signal.h>
38 #include <errno.h>
39 #include <fcntl.h>
40
41 #include <string>
42 #include <ostream>
43 #include <fstream>
44 #include <queue>
45
46 using namespace X265_NS;
47
48 #define X265_HEAD_ENTRIES 3
49
50 #ifdef _WIN32
51 #define strdup _strdup
52 #endif
53
54 #ifdef _WIN32
55 /* Copy of x264 code, which allows for Unicode characters in the command line.
56 * Retrieve command line arguments as UTF-8. */
get_argv_utf8(int * argc_ptr,char *** argv_ptr)57 static int get_argv_utf8(int *argc_ptr, char ***argv_ptr)
58 {
59 int ret = 0;
60 wchar_t **argv_utf16 = CommandLineToArgvW(GetCommandLineW(), argc_ptr);
61 if (argv_utf16)
62 {
63 int argc = *argc_ptr;
64 int offset = (argc + 1) * sizeof(char*);
65 int size = offset;
66
67 for (int i = 0; i < argc; i++)
68 size += WideCharToMultiByte(CP_UTF8, 0, argv_utf16[i], -1, NULL, 0, NULL, NULL);
69
70 char **argv = *argv_ptr = (char**)malloc(size);
71 if (argv)
72 {
73 for (int i = 0; i < argc; i++)
74 {
75 argv[i] = (char*)argv + offset;
76 offset += WideCharToMultiByte(CP_UTF8, 0, argv_utf16[i], -1, argv[i], size - offset, NULL, NULL);
77 }
78 argv[argc] = NULL;
79 ret = 1;
80 }
81 LocalFree(argv_utf16);
82 }
83 return ret;
84 }
85 #endif
86
87 /* Checks for abr-ladder config file in the command line.
88 * Returns true if abr-config file is present. Returns
89 * false otherwise */
90
checkAbrLadder(int argc,char ** argv,FILE ** abrConfig)91 static bool checkAbrLadder(int argc, char **argv, FILE **abrConfig)
92 {
93 for (optind = 0;;)
94 {
95 int long_options_index = -1;
96 int c = getopt_long(argc, argv, short_options, long_options, &long_options_index);
97 if (c == -1)
98 break;
99 if (long_options_index < 0 && c > 0)
100 {
101 for (size_t i = 0; i < sizeof(long_options) / sizeof(long_options[0]); i++)
102 {
103 if (long_options[i].val == c)
104 {
105 long_options_index = (int)i;
106 break;
107 }
108 }
109
110 if (long_options_index < 0)
111 {
112 /* getopt_long might have already printed an error message */
113 if (c != 63)
114 x265_log(NULL, X265_LOG_WARNING, "internal error: short option '%c' has no long option\n", c);
115 return false;
116 }
117 }
118 if (long_options_index < 0)
119 {
120 x265_log(NULL, X265_LOG_WARNING, "short option '%c' unrecognized\n", c);
121 return false;
122 }
123 if (!strcmp(long_options[long_options_index].name, "abr-ladder"))
124 {
125 *abrConfig = x265_fopen(optarg, "rb");
126 if (!abrConfig)
127 x265_log_file(NULL, X265_LOG_ERROR, "%s abr-ladder config file not found or error in opening zone file\n", optarg);
128 return true;
129 }
130 }
131 return false;
132 }
133
getNumAbrEncodes(FILE * abrConfig)134 static uint8_t getNumAbrEncodes(FILE* abrConfig)
135 {
136 char line[1024];
137 uint8_t numEncodes = 0;
138
139 while (fgets(line, sizeof(line), abrConfig))
140 {
141 if (strcmp(line, "\n") == 0)
142 continue;
143 else if (!(*line == '#'))
144 numEncodes++;
145 }
146 rewind(abrConfig);
147 return numEncodes;
148 }
149
parseAbrConfig(FILE * abrConfig,CLIOptions cliopt[],uint8_t numEncodes)150 static bool parseAbrConfig(FILE* abrConfig, CLIOptions cliopt[], uint8_t numEncodes)
151 {
152 char line[1024];
153 char* argLine;
154
155 for (uint32_t i = 0; i < numEncodes; i++)
156 {
157 fgets(line, sizeof(line), abrConfig);
158 if (*line == '#' || (strcmp(line, "\r\n") == 0))
159 continue;
160 int index = (int)strcspn(line, "\r\n");
161 line[index] = '\0';
162 argLine = line;
163 char* start = strchr(argLine, ' ');
164 while (isspace((unsigned char)*start)) start++;
165 int argc = 0;
166 char **argv = (char**)malloc(256 * sizeof(char *));
167 // Adding a dummy string to avoid file parsing error
168 argv[argc++] = (char *)"x265";
169
170 /* Parse CLI header to identify the ID of the load encode and the reuse level */
171 char *header = strtok(argLine, "[]");
172 uint32_t idCount = 0;
173 char *id = strtok(header, ":");
174 char *head[X265_HEAD_ENTRIES];
175 cliopt[i].encId = i;
176 cliopt[i].isAbrLadderConfig = true;
177
178 while (id && (idCount <= X265_HEAD_ENTRIES))
179 {
180 head[idCount] = id;
181 id = strtok(NULL, ":");
182 idCount++;
183 }
184 if (idCount != X265_HEAD_ENTRIES)
185 {
186 x265_log(NULL, X265_LOG_ERROR, "Incorrect number of arguments in ABR CLI header at line %d\n", i);
187 return false;
188 }
189 else
190 {
191 cliopt[i].encName = strdup(head[0]);
192 cliopt[i].loadLevel = atoi(head[1]);
193 cliopt[i].reuseName = strdup(head[2]);
194 }
195
196 char* token = strtok(start, " ");
197 while (token)
198 {
199 argv[argc++] = strdup(token);
200 token = strtok(NULL, " ");
201 }
202 argv[argc] = NULL;
203 if (cliopt[i].parse(argc++, argv))
204 {
205 cliopt[i].destroy();
206 if (cliopt[i].api)
207 cliopt[i].api->param_free(cliopt[i].param);
208 exit(1);
209 }
210 }
211 return true;
212 }
213
setRefContext(CLIOptions cliopt[],uint32_t numEncodes)214 static bool setRefContext(CLIOptions cliopt[], uint32_t numEncodes)
215 {
216 bool hasRef = false;
217 bool isRefFound = false;
218
219 /* Identify reference encode IDs and set save/load reuse levels */
220 for (uint32_t curEnc = 0; curEnc < numEncodes; curEnc++)
221 {
222 isRefFound = false;
223 hasRef = !strcmp(cliopt[curEnc].reuseName, "nil") ? false : true;
224 if (hasRef)
225 {
226 for (uint32_t refEnc = 0; refEnc < numEncodes; refEnc++)
227 {
228 if (!strcmp(cliopt[curEnc].reuseName, cliopt[refEnc].encName))
229 {
230 cliopt[curEnc].refId = refEnc;
231 cliopt[refEnc].numRefs++;
232 cliopt[refEnc].saveLevel = X265_MAX(cliopt[refEnc].saveLevel, cliopt[curEnc].loadLevel);
233 isRefFound = true;
234 break;
235 }
236 }
237 if (!isRefFound)
238 {
239 x265_log(NULL, X265_LOG_ERROR, "Reference encode (%s) not found for %s\n", cliopt[curEnc].reuseName,
240 cliopt[curEnc].encName);
241 return false;
242 }
243 }
244 }
245 return true;
246 }
247 /* CLI return codes:
248 *
249 * 0 - encode successful
250 * 1 - unable to parse command line
251 * 2 - unable to open encoder
252 * 3 - unable to generate stream headers
253 * 4 - encoder abort */
254
main(int argc,char ** argv)255 int main(int argc, char **argv)
256 {
257 #if HAVE_VLD
258 // This uses Microsoft's proprietary WCHAR type, but this only builds on Windows to start with
259 VLDSetReportOptions(VLD_OPT_REPORT_TO_DEBUGGER | VLD_OPT_REPORT_TO_FILE, L"x265_leaks.txt");
260 #endif
261 PROFILE_INIT();
262 THREAD_NAME("API", 0);
263
264 GetConsoleTitle(orgConsoleTitle, CONSOLE_TITLE_SIZE);
265 SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED);
266 #if _WIN32
267 char** orgArgv = argv;
268 get_argv_utf8(&argc, &argv);
269 #endif
270
271 uint8_t numEncodes = 1;
272 FILE *abrConfig = NULL;
273 bool isAbrLadder = checkAbrLadder(argc, argv, &abrConfig);
274
275 if (isAbrLadder)
276 numEncodes = getNumAbrEncodes(abrConfig);
277
278 CLIOptions* cliopt = new CLIOptions[numEncodes];
279
280 if (isAbrLadder)
281 {
282 if (!parseAbrConfig(abrConfig, cliopt, numEncodes))
283 exit(1);
284 if (!setRefContext(cliopt, numEncodes))
285 exit(1);
286 }
287 else if (cliopt[0].parse(argc, argv))
288 {
289 cliopt[0].destroy();
290 if (cliopt[0].api)
291 cliopt[0].api->param_free(cliopt[0].param);
292 exit(1);
293 }
294
295 int ret = 0;
296
297 AbrEncoder* abrEnc = new AbrEncoder(cliopt, numEncodes, ret);
298 int threadsActive = abrEnc->m_numActiveEncodes.get();
299 while (threadsActive)
300 {
301 threadsActive = abrEnc->m_numActiveEncodes.waitForChange(threadsActive);
302 for (uint8_t idx = 0; idx < numEncodes; idx++)
303 {
304 if (abrEnc->m_passEnc[idx]->m_ret)
305 {
306 if (isAbrLadder)
307 x265_log(NULL, X265_LOG_INFO, "Error generating ABR-ladder \n");
308 ret = abrEnc->m_passEnc[idx]->m_ret;
309 threadsActive = 0;
310 break;
311 }
312 }
313 }
314
315 abrEnc->destroy();
316 delete abrEnc;
317
318 for (uint8_t idx = 0; idx < numEncodes; idx++)
319 cliopt[idx].destroy();
320
321 delete[] cliopt;
322
323 SetConsoleTitle(orgConsoleTitle);
324 SetThreadExecutionState(ES_CONTINUOUS);
325
326 #if _WIN32
327 if (argv != orgArgv)
328 {
329 free(argv);
330 argv = orgArgv;
331 }
332 #endif
333
334 #if HAVE_VLD
335 assert(VLDReportLeaks() == 0);
336 #endif
337
338 return ret;
339 }
340