MobileFFmpeg Android API  2.2
mobileffmpeg_config.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018 Taner Sener
3  *
4  * This file is part of MobileFFmpeg.
5  *
6  * MobileFFmpeg is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * MobileFFmpeg 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 Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with MobileFFmpeg. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*
21  * CHANGES 08.2018
22  * --------------------------------------------------------
23  * - Copied methods with avutil_log_ prefix from libavutil/log.c
24  */
25 
26 #include <pthread.h>
27 
28 #include "fftools_ffmpeg.h"
29 #include "mobileffmpeg_config.h"
30 #include "libavutil/bprint.h"
31 
33 struct CallbackData {
34  int type; // 1 (log callback) or 2 (statistics callback)
35 
36  int logLevel; // log level
37  char *logData; // log data
38 
39  int statisticsFrameNumber; // statistics frame number
40  float statisticsFps; // statistics fps
41  float statisticsQuality; // statistics quality
42  int64_t statisticsSize; // statistics size
43  int statisticsTime; // statistics time
44  double statisticsBitrate; // statistics bitrate
45  double statisticsSpeed; // statistics speed
46 
47  struct CallbackData *next;
48 };
49 
51 pthread_mutex_t lockMutex;
52 pthread_mutex_t monitorMutex;
53 pthread_cond_t monitorCondition;
54 
55 pthread_t callbackThread;
57 
60 
62 static JavaVM *globalVm;
63 
65 static jclass configClass;
66 
68 static jmethodID logMethod;
69 
71 static jmethodID statisticsMethod;
72 
74 const char *configClassName = "com/arthenica/mobileffmpeg/Config";
75 
77 JNINativeMethod configMethods[] = {
78  {"enableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection},
79  {"disableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_disableNativeRedirection},
80  {"setNativeLogLevel", "(I)V", (void*) Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel},
81  {"getNativeLogLevel", "()I", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel}
82 };
83 
85 #define LOG_LINE_SIZE 1024
86 
87 static const char *avutil_log_get_level_str(int level) {
88  switch (level) {
89  case AV_LOG_QUIET:
90  return "quiet";
91  case AV_LOG_DEBUG:
92  return "debug";
93  case AV_LOG_VERBOSE:
94  return "verbose";
95  case AV_LOG_INFO:
96  return "info";
97  case AV_LOG_WARNING:
98  return "warning";
99  case AV_LOG_ERROR:
100  return "error";
101  case AV_LOG_FATAL:
102  return "fatal";
103  case AV_LOG_PANIC:
104  return "panic";
105  default:
106  return "";
107  }
108 }
109 
110 static void avutil_log_format_line(void *avcl, int level, const char *fmt, va_list vl, AVBPrint part[4], int *print_prefix) {
111  int flags = av_log_get_flags();
112  AVClass* avc = avcl ? *(AVClass **) avcl : NULL;
113  av_bprint_init(part+0, 0, 1);
114  av_bprint_init(part+1, 0, 1);
115  av_bprint_init(part+2, 0, 1);
116  av_bprint_init(part+3, 0, 65536);
117 
118  if (*print_prefix && avc) {
119  if (avc->parent_log_context_offset) {
120  AVClass** parent = *(AVClass ***) (((uint8_t *) avcl) +
121  avc->parent_log_context_offset);
122  if (parent && *parent) {
123  av_bprintf(part+0, "[%s @ %p] ",
124  (*parent)->item_name(parent), parent);
125  }
126  }
127  av_bprintf(part+1, "[%s @ %p] ",
128  avc->item_name(avcl), avcl);
129  }
130 
131  if (*print_prefix && (level > AV_LOG_QUIET) && (flags & AV_LOG_PRINT_LEVEL))
132  av_bprintf(part+2, "[%s] ", avutil_log_get_level_str(level));
133 
134  av_vbprintf(part+3, fmt, vl);
135 
136  if(*part[0].str || *part[1].str || *part[2].str || *part[3].str) {
137  char lastc = part[3].len && part[3].len <= part[3].size ? part[3].str[part[3].len - 1] : 0;
138  *print_prefix = lastc == '\n' || lastc == '\r';
139  }
140 }
141 
142 static void avutil_log_sanitize(uint8_t *line) {
143  while(*line){
144  if(*line < 0x08 || (*line > 0x0D && *line < 0x20))
145  *line='?';
146  line++;
147  }
148 }
149 
150 void mutexInit() {
151  pthread_mutexattr_t attributes;
152  pthread_mutexattr_init(&attributes);
153  pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
154 
155  pthread_mutex_init(&lockMutex, &attributes);
156  pthread_mutexattr_destroy(&attributes);
157 }
158 
159 void monitorInit() {
160  pthread_mutexattr_t attributes;
161  pthread_mutexattr_init(&attributes);
162  pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
163 
164  pthread_condattr_t cattributes;
165  pthread_condattr_init(&cattributes);
166  pthread_condattr_setpshared(&cattributes, PTHREAD_PROCESS_PRIVATE);
167 
168  pthread_mutex_init(&monitorMutex, &attributes);
169  pthread_mutexattr_destroy(&attributes);
170 
171  pthread_cond_init(&monitorCondition, &cattributes);
172  pthread_condattr_destroy(&cattributes);
173 }
174 
175 void mutexUnInit() {
176  pthread_mutex_destroy(&lockMutex);
177 }
178 
180  pthread_mutex_destroy(&monitorMutex);
181  pthread_cond_destroy(&monitorCondition);
182 }
183 
184 void mutexLock() {
185  pthread_mutex_lock(&lockMutex);
186 }
187 
188 void mutexUnlock() {
189  pthread_mutex_unlock(&lockMutex);
190 }
191 
192 void monitorWait(int milliSeconds) {
193  struct timeval tp;
194  struct timespec ts;
195  int rc;
196 
197  rc = gettimeofday(&tp, NULL);
198  if (rc) {
199  return;
200  }
201 
202  ts.tv_sec = tp.tv_sec;
203  ts.tv_nsec = tp.tv_usec * 1000;
204  ts.tv_sec += milliSeconds / 1000;
205  ts.tv_nsec += (milliSeconds % 1000)*1000000;
206 
207  pthread_mutex_lock(&monitorMutex);
208  pthread_cond_timedwait(&monitorCondition, &monitorMutex, &ts);
209  pthread_mutex_unlock(&monitorMutex);
210 }
211 
213  pthread_mutex_lock(&monitorMutex);
214  pthread_cond_signal(&monitorCondition);
215  pthread_mutex_unlock(&monitorMutex);
216 }
217 
221 void logCallbackDataAdd(int level, const char *data) {
222 
223  // CREATE DATA STRUCT FIRST
224  struct CallbackData *newData = (struct CallbackData*)malloc(sizeof(struct CallbackData));
225  newData->type = 1;
226  newData->logLevel = level;
227  size_t dataSize = strlen(data) + 1;
228  newData->logData = (char*)malloc(dataSize);
229  memcpy(newData->logData, data, dataSize);
230  newData->next = NULL;
231 
232  mutexLock();
233 
234  // INSERT IT TO THE END OF QUEUE
235  if (callbackDataTail == NULL) {
236  callbackDataTail = newData;
237 
238  if (callbackDataHead != NULL) {
239  LOGE("Dangling callback data head detected. This can cause memory leak.");
240  } else {
241  callbackDataHead = newData;
242  }
243  } else {
244  struct CallbackData *oldTail = callbackDataTail;
245  oldTail->next = newData;
246 
247  callbackDataTail = newData;
248  }
249 
250  mutexUnlock();
251 
252  monitorNotify();
253 }
254 
258 void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) {
259 
260  // CREATE DATA STRUCT FIRST
261  struct CallbackData *newData = (struct CallbackData*)malloc(sizeof(struct CallbackData));
262  newData->type = 2;
263  newData->statisticsFrameNumber = frameNumber;
264  newData->statisticsFps = fps;
265  newData->statisticsQuality = quality;
266  newData->statisticsSize = size;
267  newData->statisticsTime = time;
268  newData->statisticsBitrate = bitrate;
269  newData->statisticsSpeed = speed;
270 
271  newData->next = NULL;
272 
273  mutexLock();
274 
275  // INSERT IT TO THE END OF QUEUE
276  if (callbackDataTail == NULL) {
277  callbackDataTail = newData;
278 
279  if (callbackDataHead != NULL) {
280  LOGE("Dangling callback data head detected. This can cause memory leak.");
281  } else {
282  callbackDataHead = newData;
283  }
284  } else {
285  struct CallbackData *oldTail = callbackDataTail;
286  oldTail->next = newData;
287 
288  callbackDataTail = newData;
289  }
290 
291  mutexUnlock();
292 
293  monitorNotify();
294 }
295 
300  struct CallbackData *currentData;
301 
302  mutexLock();
303 
304  if (callbackDataHead == NULL) {
305  currentData = NULL;
306  } else {
307  currentData = callbackDataHead;
308 
309  struct CallbackData *nextHead = currentData->next;
310  if (nextHead == NULL) {
312  LOGE("Head and tail callback data pointers do not match for single callback data element. This can cause memory leak.");
313  } else {
314  callbackDataTail = NULL;
315  }
316  callbackDataHead = NULL;
317 
318  } else {
319  callbackDataHead = nextHead;
320  }
321  }
322 
323  mutexUnlock();
324 
325  return currentData;
326 }
327 
336 void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format, va_list vargs) {
337  char line[LOG_LINE_SIZE];
338  AVBPrint part[4];
339  int print_prefix = 1;
340 
341  if (level >= 0) {
342  level &= 0xff;
343  }
344 
345  avutil_log_format_line(ptr, level, format, vargs, part, &print_prefix);
346  snprintf(line, sizeof(line), "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str);
347 
348  avutil_log_sanitize(part[0].str);
349  logCallbackDataAdd(level, part[0].str);
350  avutil_log_sanitize(part[1].str);
351  logCallbackDataAdd(level, part[1].str);
352  avutil_log_sanitize(part[2].str);
353  logCallbackDataAdd(level, part[2].str);
354  avutil_log_sanitize(part[3].str);
355  logCallbackDataAdd(level, part[3].str);
356 
357  av_bprint_finalize(part+3, NULL);
358 }
359 
371 void mobileffmpeg_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) {
372  statisticsCallbackDataAdd(frameNumber, fps, quality, size, time, bitrate, speed);
373 }
374 
379  JNIEnv *env;
380  jint getEnvRc = (*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6);
381  if (getEnvRc != JNI_OK) {
382  if (getEnvRc != JNI_EDETACHED) {
383  LOGE("Callback thread failed to GetEnv for class %s with rc %d.\n", configClassName, getEnvRc);
384  return NULL;
385  }
386 
387  if ((*globalVm)->AttachCurrentThread(globalVm, &env, NULL) != 0) {
388  LOGE("Callback thread failed to AttachCurrentThread for class %s.\n", configClassName);
389  return NULL;
390  }
391  }
392 
393  LOGD("Callback thread started.\n");
394 
395  while(redirectionEnabled) {
396 
397  struct CallbackData *callbackData = callbackDataRemove();
398  if (callbackData != NULL) {
399  if (callbackData->type == 1) {
400 
401  // LOG CALLBACK
402 
403  size_t size = strlen(callbackData->logData);
404 
405  jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size);
406  (*env)->SetByteArrayRegion(env, byteArray, 0, size, (jbyte *)callbackData->logData);
407  (*env)->CallStaticVoidMethod(env, configClass, logMethod, callbackData->logLevel, byteArray);
408  (*env)->DeleteLocalRef(env, byteArray);
409 
410  // CLEAN LOG DATA
411  free(callbackData->logData);
412 
413  } else {
414 
415  // STATISTICS CALLBACK
416 
417  (*env)->CallStaticVoidMethod(env, configClass, statisticsMethod,
418  callbackData->statisticsFrameNumber, callbackData->statisticsFps,
419  callbackData->statisticsQuality, callbackData->statisticsSize,
420  callbackData->statisticsTime, callbackData->statisticsBitrate,
421  callbackData->statisticsSpeed);
422 
423  }
424 
425  // CLEAN STRUCT
426  callbackData->next = NULL;
427  free(callbackData);
428 
429  } else {
430  monitorWait(100);
431  }
432  }
433 
434  (*globalVm)->DetachCurrentThread(globalVm);
435 
436  LOGD("Callback thread stopped.\n");
437 
438  return NULL;
439 }
440 
448 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
449  JNIEnv *env;
450  if ((*vm)->GetEnv(vm, (void**)(&env), JNI_VERSION_1_6) != JNI_OK) {
451  LOGE("OnLoad failed to GetEnv for class %s.\n", configClassName);
452  return JNI_FALSE;
453  }
454 
455  jclass localConfigClass = (*env)->FindClass(env, configClassName);
456  if (localConfigClass == NULL) {
457  LOGE("OnLoad failed to FindClass %s.\n", configClassName);
458  return JNI_FALSE;
459  }
460 
461  if ((*env)->RegisterNatives(env, localConfigClass, configMethods, 4) < 0) {
462  LOGE("OnLoad failed to RegisterNatives for class %s.\n", configClassName);
463  return JNI_FALSE;
464  }
465 
466  (*env)->GetJavaVM(env, &globalVm);
467 
468  logMethod = (*env)->GetStaticMethodID(env, localConfigClass, "log", "(I[B)V");
469  if (logMethod == NULL) {
470  LOGE("OnLoad thread failed to GetMethodID for %s.\n", "log");
471  (*globalVm)->DetachCurrentThread(globalVm);
472  return JNI_FALSE;
473  }
474 
475  statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(IFFJIDD)V");
476  if (logMethod == NULL) {
477  LOGE("OnLoad thread failed to GetMethodID for %s.\n", "statistics");
478  (*globalVm)->DetachCurrentThread(globalVm);
479  return JNI_FALSE;
480  }
481 
482  configClass = (jclass) ((*env)->NewGlobalRef(env, localConfigClass));
483 
484  redirectionEnabled = 0;
485 
486  callbackDataHead = NULL;
487  callbackDataTail = NULL;
488 
489  mutexInit();
490  monitorInit();
491 
492  return JNI_VERSION_1_6;
493 }
494 
502 JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *env, jclass object, jint level) {
503  av_log_set_level(level);
504 }
505 
512 JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *env, jclass object) {
513  return av_log_get_level();
514 }
515 
522 JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection(JNIEnv *env, jclass object) {
523  mutexLock();
524 
525  if (redirectionEnabled != 0) {
526  mutexUnlock();
527  return;
528  }
529  redirectionEnabled = 1;
530 
531  mutexUnlock();
532 
533  int rc = pthread_create(&callbackThread, 0, callbackThreadFunction, 0);
534  if (rc != 0) {
535  LOGE("Failed to create callback thread (rc=%d).\n", rc);
536  return;
537  }
538 
539  av_log_set_callback(mobileffmpeg_log_callback_function);
541 }
542 
549 JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_disableNativeRedirection(JNIEnv *env, jclass object) {
550 
551  mutexLock();
552 
553  if (redirectionEnabled != 1) {
554  mutexUnlock();
555  return;
556  }
557  redirectionEnabled = 0;
558 
559  mutexUnlock();
560 
561  av_log_set_callback(av_log_default_callback);
562  set_report_callback(NULL);
563 
564  monitorNotify();
565 }
void monitorInit()
static jmethodID statisticsMethod
void monitorNotify()
void mutexLock()
void mutexUnInit()
static jmethodID logMethod
JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *env, jclass object)
pthread_mutex_t lockMutex
static void avutil_log_sanitize(uint8_t *line)
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection(JNIEnv *env, jclass object)
static JavaVM * globalVm
#define LOGE(...)
pthread_mutex_t monitorMutex
const char * configClassName
void logCallbackDataAdd(int level, const char *data)
pthread_cond_t monitorCondition
JNINativeMethod configMethods[]
struct CallbackData * next
void monitorUnInit()
void * callbackThreadFunction()
void mutexInit()
static void avutil_log_format_line(void *avcl, int level, const char *fmt, va_list vl, AVBPrint part[4], int *print_prefix)
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *env, jclass object, jint level)
void mobileffmpeg_log_callback_function(void *ptr, int level, const char *format, va_list vargs)
void mutexUnlock()
JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_disableNativeRedirection(JNIEnv *env, jclass object)
void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed)
#define LOG_LINE_SIZE
void set_report_callback(void(*callback)(int, float, float, int64_t, int, double, double))
struct CallbackData * callbackDataTail
int redirectionEnabled
jint JNI_OnLoad(JavaVM *vm, void *reserved)
void monitorWait(int milliSeconds)
struct CallbackData * callbackDataRemove()
pthread_t callbackThread
static const char * avutil_log_get_level_str(int level)
struct CallbackData * callbackDataHead
static jclass configClass
#define LOGD(...)
void mobileffmpeg_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed)