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