android_opengl.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. /*
  2. * Copyright (C) 2013-2014 Teluu Inc. (http://www.teluu.com)
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software
  16. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17. */
  18. #include <pjmedia-videodev/videodev_imp.h>
  19. #include <pj/assert.h>
  20. #include <pj/log.h>
  21. #include <pj/os.h>
  22. #if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \
  23. defined(PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL) && \
  24. PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL != 0
  25. #include <pjmedia-videodev/opengl_dev.h>
  26. #include <jni.h>
  27. #include <android/native_window_jni.h>
  28. #include <EGL/egl.h>
  29. #include <GLES2/gl2.h>
  30. #include <GLES2/gl2ext.h>
  31. #define THIS_FILE "android_opengl.cpp"
  32. #define MAX_JOBS 1
  33. /* Define the number of errors before the stream stops trying to do rendering.
  34. * To disable this feature, put 0.
  35. */
  36. #define STOP_IF_ERROR_RENDERING 8
  37. typedef struct andgl_fmt_info
  38. {
  39. pjmedia_format_id pjmedia_format;
  40. } andgl_fmt_info;
  41. /* Supported formats */
  42. static andgl_fmt_info andgl_fmts[] =
  43. {
  44. {PJMEDIA_FORMAT_BGRA}
  45. };
  46. typedef pj_status_t (*job_func_ptr)(void *data);
  47. typedef struct job {
  48. job_func_ptr func;
  49. void *data;
  50. unsigned flags;
  51. pj_status_t retval;
  52. } job;
  53. typedef struct job_queue {
  54. job *jobs[MAX_JOBS];
  55. pj_sem_t *job_sem[MAX_JOBS];
  56. pj_mutex_t *mutex;
  57. pj_thread_t *thread;
  58. pj_sem_t *sem;
  59. unsigned size;
  60. unsigned head, tail;
  61. pj_bool_t is_quitting;
  62. } job_queue;
  63. /* Video stream. */
  64. struct andgl_stream
  65. {
  66. pjmedia_vid_dev_stream base; /**< Base stream */
  67. pjmedia_vid_dev_param param; /**< Settings */
  68. pj_pool_t *pool; /**< Memory pool */
  69. pjmedia_vid_dev_cb vid_cb; /**< Stream callback */
  70. void *user_data; /**< Application data */
  71. pj_timestamp frame_ts;
  72. unsigned ts_inc;
  73. pjmedia_rect_size vid_size;
  74. job_queue *jq;
  75. pj_bool_t is_running;
  76. pj_int32_t err_rend;
  77. const pjmedia_frame *frame;
  78. gl_buffers *gl_buf;
  79. EGLDisplay display;
  80. EGLSurface surface;
  81. EGLContext context;
  82. ANativeWindow *window;
  83. };
  84. /* Prototypes */
  85. static pj_status_t andgl_stream_get_param(pjmedia_vid_dev_stream *strm,
  86. pjmedia_vid_dev_param *param);
  87. static pj_status_t andgl_stream_get_cap(pjmedia_vid_dev_stream *strm,
  88. pjmedia_vid_dev_cap cap,
  89. void *value);
  90. static pj_status_t andgl_stream_set_cap(pjmedia_vid_dev_stream *strm,
  91. pjmedia_vid_dev_cap cap,
  92. const void *value);
  93. static pj_status_t andgl_stream_start(pjmedia_vid_dev_stream *strm);
  94. static pj_status_t andgl_stream_put_frame(pjmedia_vid_dev_stream *strm,
  95. const pjmedia_frame *frame);
  96. static pj_status_t andgl_stream_stop(pjmedia_vid_dev_stream *strm);
  97. static pj_status_t andgl_stream_destroy(pjmedia_vid_dev_stream *strm);
  98. static pj_status_t init_opengl(void * data);
  99. static pj_status_t deinit_opengl(void * data);
  100. /* Job queue prototypes */
  101. static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq);
  102. static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
  103. void *data, unsigned flags,
  104. pj_status_t *retval);
  105. static pj_status_t job_queue_destroy(job_queue *jq);
  106. /* Operations */
  107. static pjmedia_vid_dev_stream_op stream_op =
  108. {
  109. &andgl_stream_get_param,
  110. &andgl_stream_get_cap,
  111. &andgl_stream_set_cap,
  112. &andgl_stream_start,
  113. NULL,
  114. &andgl_stream_put_frame,
  115. &andgl_stream_stop,
  116. &andgl_stream_destroy
  117. };
  118. int pjmedia_vid_dev_opengl_imp_get_cap(void)
  119. {
  120. return PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
  121. }
  122. static andgl_fmt_info* get_andgl_format_info(pjmedia_format_id id)
  123. {
  124. unsigned i;
  125. for (i = 0; i < PJ_ARRAY_SIZE(andgl_fmts); i++) {
  126. if (andgl_fmts[i].pjmedia_format == id)
  127. return &andgl_fmts[i];
  128. }
  129. return NULL;
  130. }
  131. #define EGL_ERR(str) \
  132. { \
  133. PJ_LOG(3, (THIS_FILE, "Unable to %s %d", str, eglGetError())); \
  134. status = PJMEDIA_EVID_SYSERR; \
  135. goto on_return; \
  136. }
  137. static pj_status_t init_opengl(void * data)
  138. {
  139. struct andgl_stream *strm = (struct andgl_stream *)data;
  140. const EGLint attr[] =
  141. {
  142. EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE,
  143. EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8,
  144. EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 8, EGL_NONE
  145. };
  146. EGLint context_attr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
  147. EGLConfig config;
  148. EGLint numConfigs;
  149. EGLint format;
  150. EGLint width;
  151. EGLint height;
  152. pj_status_t status;
  153. strm->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  154. if (strm->display == EGL_NO_DISPLAY ||
  155. !eglInitialize(strm->display, NULL, NULL))
  156. {
  157. EGL_ERR("initialize OpenGL display");
  158. }
  159. if (!eglChooseConfig(strm->display, attr, &config, 1, &numConfigs) ||
  160. (!eglGetConfigAttrib(strm->display, config, EGL_NATIVE_VISUAL_ID,
  161. &format)))
  162. {
  163. EGL_ERR("configure OpenGL display");
  164. }
  165. if (ANativeWindow_setBuffersGeometry(strm->window, strm->param.disp_size.w,
  166. strm->param.disp_size.h, format) != 0)
  167. {
  168. EGL_ERR("set window geometry");
  169. }
  170. strm->surface = eglCreateWindowSurface(strm->display, config,
  171. strm->window, 0);
  172. if (strm->surface == EGL_NO_SURFACE)
  173. EGL_ERR("create window surface");
  174. strm->context = eglCreateContext(strm->display, config, EGL_NO_CONTEXT,
  175. context_attr);
  176. if (strm->context == EGL_NO_CONTEXT)
  177. EGL_ERR("create OpenGL context");
  178. if (!eglMakeCurrent(strm->display, strm->surface, strm->surface,
  179. strm->context))
  180. {
  181. EGL_ERR("make OpenGL as current context");
  182. }
  183. if (!eglQuerySurface(strm->display, strm->surface, EGL_WIDTH, &width) ||
  184. !eglQuerySurface(strm->display, strm->surface, EGL_HEIGHT, &height))
  185. {
  186. EGL_ERR("query surface");
  187. }
  188. /* Create GL buffers */
  189. pjmedia_vid_dev_opengl_create_buffers(strm->pool, PJ_TRUE, &strm->gl_buf);
  190. /* Init GL buffers */
  191. status = pjmedia_vid_dev_opengl_init_buffers(strm->gl_buf);
  192. on_return:
  193. if (status != PJ_SUCCESS)
  194. deinit_opengl(strm);
  195. return status;
  196. }
  197. #undef EGL_ERR
  198. static pj_status_t render(void * data)
  199. {
  200. struct andgl_stream *stream = (struct andgl_stream *)data;
  201. if (stream->display == EGL_NO_DISPLAY || stream->err_rend == 0)
  202. return PJ_SUCCESS;
  203. pjmedia_vid_dev_opengl_draw(stream->gl_buf, stream->vid_size.w,
  204. stream->vid_size.h, stream->frame->buf);
  205. if (!eglSwapBuffers(stream->display, stream->surface)) {
  206. if (eglGetError() == EGL_BAD_SURFACE && stream->err_rend > 0) {
  207. stream->err_rend--;
  208. if (stream->err_rend == 0) {
  209. PJ_LOG(3, (THIS_FILE, "Stopping OpenGL rendering due to "
  210. "consecutive errors. If app is in bg,"
  211. "it's advisable to stop the stream."));
  212. }
  213. }
  214. return eglGetError();
  215. }
  216. return PJ_SUCCESS;
  217. }
  218. static pj_status_t deinit_opengl(void * data)
  219. {
  220. struct andgl_stream *stream = (struct andgl_stream *)data;
  221. if (stream->gl_buf) {
  222. pjmedia_vid_dev_opengl_destroy_buffers(stream->gl_buf);
  223. stream->gl_buf = NULL;
  224. }
  225. if (stream->display != EGL_NO_DISPLAY) {
  226. eglMakeCurrent(stream->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
  227. EGL_NO_CONTEXT);
  228. if (stream->context != EGL_NO_CONTEXT)
  229. eglDestroyContext(stream->display, stream->context);
  230. if (stream->surface != EGL_NO_SURFACE)
  231. eglDestroySurface(stream->display, stream->surface);
  232. eglTerminate(stream->display);
  233. }
  234. stream->display = EGL_NO_DISPLAY;
  235. stream->surface = EGL_NO_SURFACE;
  236. stream->context = EGL_NO_CONTEXT;
  237. return PJ_SUCCESS;
  238. }
  239. /* API: create stream */
  240. pj_status_t
  241. pjmedia_vid_dev_opengl_imp_create_stream(pj_pool_t *pool,
  242. pjmedia_vid_dev_param *param,
  243. const pjmedia_vid_dev_cb *cb,
  244. void *user_data,
  245. pjmedia_vid_dev_stream **p_vid_strm)
  246. {
  247. struct andgl_stream *strm;
  248. const pjmedia_video_format_detail *vfd;
  249. pj_status_t status = PJ_SUCCESS;
  250. strm = PJ_POOL_ZALLOC_T(pool, struct andgl_stream);
  251. pj_memcpy(&strm->param, param, sizeof(*param));
  252. strm->pool = pool;
  253. pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
  254. strm->user_data = user_data;
  255. strm->display = EGL_NO_DISPLAY;
  256. vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE);
  257. strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
  258. /* Set video format */
  259. status = andgl_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_FORMAT,
  260. &param->fmt);
  261. if (status != PJ_SUCCESS)
  262. goto on_error;
  263. status = job_queue_create(pool, &strm->jq);
  264. if (status != PJ_SUCCESS)
  265. goto on_error;
  266. if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
  267. status = andgl_stream_set_cap(&strm->base,
  268. PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
  269. &param->window);
  270. }
  271. if (status != PJ_SUCCESS) {
  272. PJ_LOG(3, (THIS_FILE, "Failed to initialize OpenGL with the specified"
  273. " output window"));
  274. goto on_error;
  275. }
  276. PJ_LOG(4, (THIS_FILE, "Android OpenGL ES renderer successfully created"));
  277. /* Done */
  278. strm->base.op = &stream_op;
  279. *p_vid_strm = &strm->base;
  280. return PJ_SUCCESS;
  281. on_error:
  282. andgl_stream_destroy((pjmedia_vid_dev_stream *)strm);
  283. return status;
  284. }
  285. /* API: Get stream info. */
  286. static pj_status_t andgl_stream_get_param(pjmedia_vid_dev_stream *s,
  287. pjmedia_vid_dev_param *pi)
  288. {
  289. struct andgl_stream *strm = (struct andgl_stream*)s;
  290. PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
  291. pj_memcpy(pi, &strm->param, sizeof(*pi));
  292. if (andgl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
  293. &pi->window) == PJ_SUCCESS)
  294. {
  295. pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
  296. }
  297. return PJ_SUCCESS;
  298. }
  299. /* API: get capability */
  300. static pj_status_t andgl_stream_get_cap(pjmedia_vid_dev_stream *s,
  301. pjmedia_vid_dev_cap cap,
  302. void *pval)
  303. {
  304. struct andgl_stream *strm = (struct andgl_stream*)s;
  305. PJ_UNUSED_ARG(strm);
  306. PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
  307. if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
  308. pjmedia_vid_dev_hwnd *wnd = (pjmedia_vid_dev_hwnd *)pval;
  309. wnd->info.android.window = strm->window;
  310. return PJ_SUCCESS;
  311. } else {
  312. return PJMEDIA_EVID_INVCAP;
  313. }
  314. }
  315. /* API: set capability */
  316. static pj_status_t andgl_stream_set_cap(pjmedia_vid_dev_stream *s,
  317. pjmedia_vid_dev_cap cap,
  318. const void *pval)
  319. {
  320. struct andgl_stream *strm = (struct andgl_stream*)s;
  321. PJ_UNUSED_ARG(strm);
  322. PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
  323. if (cap==PJMEDIA_VID_DEV_CAP_FORMAT) {
  324. const pjmedia_video_format_info *vfi;
  325. pjmedia_video_format_detail *vfd;
  326. pjmedia_format *fmt = (pjmedia_format *)pval;
  327. andgl_fmt_info *ifi;
  328. pj_status_t status = PJ_SUCCESS;
  329. if (!(ifi = get_andgl_format_info(fmt->id)))
  330. return PJMEDIA_EVID_BADFORMAT;
  331. vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
  332. fmt->id);
  333. if (!vfi)
  334. return PJMEDIA_EVID_BADFORMAT;
  335. /* Re-init OpenGL */
  336. if (strm->window)
  337. job_queue_post_job(strm->jq, deinit_opengl, strm, 0, NULL);
  338. pjmedia_format_copy(&strm->param.fmt, fmt);
  339. vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE);
  340. pj_memcpy(&strm->vid_size, &vfd->size, sizeof(vfd->size));
  341. pj_memcpy(&strm->param.disp_size, &vfd->size, sizeof(vfd->size));
  342. if (strm->window)
  343. job_queue_post_job(strm->jq, init_opengl, strm, 0, &status);
  344. PJ_PERROR(4,(THIS_FILE, status,
  345. "Re-initializing OpenGL due to format change"));
  346. return status;
  347. } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
  348. pj_status_t status = PJ_SUCCESS;
  349. pjmedia_vid_dev_hwnd *wnd = (pjmedia_vid_dev_hwnd *)pval;
  350. ANativeWindow *native_wnd = (ANativeWindow *)wnd->info.android.window;
  351. if (strm->window == native_wnd)
  352. return PJ_SUCCESS;
  353. /* Re-init OpenGL */
  354. job_queue_post_job(strm->jq, deinit_opengl, strm, 0, NULL);
  355. if (strm->window)
  356. ANativeWindow_release(strm->window);
  357. strm->window = strm->param.window.info.android.window = native_wnd;
  358. if (strm->window) {
  359. job_queue_post_job(strm->jq, init_opengl, strm, 0, &status);
  360. }
  361. PJ_PERROR(4,(THIS_FILE, status,
  362. "Re-initializing OpenGL with native window %p",
  363. strm->window));
  364. return status;
  365. }
  366. return PJMEDIA_EVID_INVCAP;
  367. }
  368. /* API: Start stream. */
  369. static pj_status_t andgl_stream_start(pjmedia_vid_dev_stream *strm)
  370. {
  371. struct andgl_stream *stream = (struct andgl_stream*)strm;
  372. stream->err_rend = STOP_IF_ERROR_RENDERING;
  373. if (!stream->err_rend) stream->err_rend = 0xFFFF;
  374. stream->is_running = PJ_TRUE;
  375. PJ_LOG(4, (THIS_FILE, "Starting Android opengl stream"));
  376. return PJ_SUCCESS;
  377. }
  378. /* API: Put frame from stream */
  379. static pj_status_t andgl_stream_put_frame(pjmedia_vid_dev_stream *strm,
  380. const pjmedia_frame *frame)
  381. {
  382. struct andgl_stream *stream = (struct andgl_stream*)strm;
  383. pj_status_t status;
  384. /* Video conference just trying to send heart beat for updating timestamp
  385. * or keep-alive, this port doesn't need any, just ignore.
  386. */
  387. if (frame->size==0 || frame->buf==NULL)
  388. return PJ_SUCCESS;
  389. if (!stream->is_running || stream->display == EGL_NO_DISPLAY)
  390. return PJ_EINVALIDOP;
  391. stream->frame = frame;
  392. job_queue_post_job(stream->jq, render, strm, 0, &status);
  393. return status;
  394. }
  395. /* API: Stop stream. */
  396. static pj_status_t andgl_stream_stop(pjmedia_vid_dev_stream *strm)
  397. {
  398. struct andgl_stream *stream = (struct andgl_stream*)strm;
  399. stream->is_running = PJ_FALSE;
  400. PJ_LOG(4, (THIS_FILE, "Stopping Android opengl stream"));
  401. return PJ_SUCCESS;
  402. }
  403. /* API: Destroy stream. */
  404. static pj_status_t andgl_stream_destroy(pjmedia_vid_dev_stream *strm)
  405. {
  406. struct andgl_stream *stream = (struct andgl_stream*)strm;
  407. PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
  408. andgl_stream_stop(strm);
  409. job_queue_post_job(stream->jq, deinit_opengl, strm, 0, NULL);
  410. if (stream->window) {
  411. ANativeWindow_release(stream->window);
  412. stream->window = NULL;
  413. }
  414. if (stream->jq) {
  415. job_queue_destroy(stream->jq);
  416. stream->jq = NULL;
  417. }
  418. pj_pool_release(stream->pool);
  419. return PJ_SUCCESS;
  420. }
  421. static int job_thread(void * data)
  422. {
  423. job_queue *jq = (job_queue *)data;
  424. while (1) {
  425. job *jb;
  426. /* Wait until there is a job. */
  427. pj_sem_wait(jq->sem);
  428. /* Make sure there is no pending jobs before we quit. */
  429. if (jq->is_quitting && jq->head == jq->tail)
  430. break;
  431. jb = jq->jobs[jq->head];
  432. jb->retval = (*jb->func)(jb->data);
  433. pj_sem_post(jq->job_sem[jq->head]);
  434. jq->head = (jq->head + 1) % jq->size;
  435. }
  436. return 0;
  437. }
  438. static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq)
  439. {
  440. unsigned i;
  441. pj_status_t status;
  442. job_queue *jq = PJ_POOL_ZALLOC_T(pool, job_queue);
  443. jq->size = MAX_JOBS;
  444. status = pj_sem_create(pool, "thread_sem", 0, jq->size + 1, &jq->sem);
  445. if (status != PJ_SUCCESS)
  446. goto on_error;
  447. for (i = 0; i < jq->size; i++) {
  448. status = pj_sem_create(pool, "job_sem", 0, 1, &jq->job_sem[i]);
  449. if (status != PJ_SUCCESS)
  450. goto on_error;
  451. }
  452. status = pj_mutex_create_recursive(pool, "job_mutex", &jq->mutex);
  453. if (status != PJ_SUCCESS)
  454. goto on_error;
  455. status = pj_thread_create(pool, "job_th", job_thread, jq, 0, 0,
  456. &jq->thread);
  457. if (status != PJ_SUCCESS)
  458. goto on_error;
  459. *pjq = jq;
  460. return PJ_SUCCESS;
  461. on_error:
  462. job_queue_destroy(jq);
  463. return status;
  464. }
  465. static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
  466. void *data, unsigned flags,
  467. pj_status_t *retval)
  468. {
  469. job jb;
  470. int tail;
  471. if (jq->is_quitting)
  472. return PJ_EBUSY;
  473. jb.func = func;
  474. jb.data = data;
  475. jb.flags = flags;
  476. pj_mutex_lock(jq->mutex);
  477. jq->jobs[jq->tail] = &jb;
  478. tail = jq->tail;
  479. jq->tail = (jq->tail + 1) % jq->size;
  480. pj_sem_post(jq->sem);
  481. /* Wait until our posted job is completed. */
  482. pj_sem_wait(jq->job_sem[tail]);
  483. pj_mutex_unlock(jq->mutex);
  484. if (retval) *retval = jb.retval;
  485. return PJ_SUCCESS;
  486. }
  487. static pj_status_t job_queue_destroy(job_queue *jq)
  488. {
  489. unsigned i;
  490. jq->is_quitting = PJ_TRUE;
  491. if (jq->thread) {
  492. pj_sem_post(jq->sem);
  493. pj_thread_join(jq->thread);
  494. pj_thread_destroy(jq->thread);
  495. }
  496. if (jq->sem) {
  497. pj_sem_destroy(jq->sem);
  498. jq->sem = NULL;
  499. }
  500. for (i = 0; i < jq->size; i++) {
  501. if (jq->job_sem[i]) {
  502. pj_sem_destroy(jq->job_sem[i]);
  503. jq->job_sem[i] = NULL;
  504. }
  505. }
  506. if (jq->mutex) {
  507. pj_mutex_destroy(jq->mutex);
  508. jq->mutex = NULL;
  509. }
  510. return PJ_SUCCESS;
  511. }
  512. #endif /* PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL */