oboe_dev.cpp 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226
  1. /*
  2. * Copyright (C) 2021 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. /* This file is the implementation of Android Oboe audio device. */
  19. #include <pjmedia-audiodev/audiodev_imp.h>
  20. #include <pj/assert.h>
  21. #include <pj/lock.h>
  22. #include <pj/log.h>
  23. #include <pj/os.h>
  24. #include <pjmedia/circbuf.h>
  25. #include <pjmedia/errno.h>
  26. #include <pjmedia/event.h>
  27. #if defined(PJMEDIA_AUDIO_DEV_HAS_OBOE) && PJMEDIA_AUDIO_DEV_HAS_OBOE != 0
  28. #define THIS_FILE "oboe_dev.cpp"
  29. #define DRIVER_NAME "Oboe"
  30. #include <jni.h>
  31. #include <semaphore.h>
  32. #include <android/log.h>
  33. #include <oboe/Oboe.h>
  34. #include <atomic>
  35. /* Device info */
  36. typedef struct aud_dev_info
  37. {
  38. pjmedia_aud_dev_info info; /**< Base info */
  39. int id; /**< Original dev ID */
  40. } aud_dev_info;
  41. /* Oboe factory */
  42. struct oboe_aud_factory
  43. {
  44. pjmedia_aud_dev_factory base;
  45. pj_pool_factory *pf;
  46. pj_pool_t *pool;
  47. pj_pool_t *dev_pool; /**< Device list pool */
  48. unsigned dev_count; /**< Device count */
  49. aud_dev_info *dev_info; /**< Device info list */
  50. };
  51. class MyOboeEngine;
  52. /*
  53. * Sound stream descriptor.
  54. * This struct may be used for both unidirectional or bidirectional sound
  55. * streams.
  56. */
  57. struct oboe_aud_stream
  58. {
  59. pjmedia_aud_stream base;
  60. pj_pool_t *pool;
  61. pj_str_t name;
  62. pjmedia_dir dir;
  63. pjmedia_aud_param param;
  64. struct oboe_aud_factory *f;
  65. int bytes_per_sample;
  66. pj_uint32_t samples_per_sec;
  67. unsigned samples_per_frame;
  68. int channel_count;
  69. void *user_data;
  70. pj_bool_t running;
  71. /* Capture/record */
  72. MyOboeEngine *rec_engine;
  73. pjmedia_aud_rec_cb rec_cb;
  74. /* Playback */
  75. MyOboeEngine *play_engine;
  76. pjmedia_aud_play_cb play_cb;
  77. };
  78. /* Factory prototypes */
  79. static pj_status_t oboe_init(pjmedia_aud_dev_factory *f);
  80. static pj_status_t oboe_destroy(pjmedia_aud_dev_factory *f);
  81. static pj_status_t oboe_refresh(pjmedia_aud_dev_factory *f);
  82. static unsigned oboe_get_dev_count(pjmedia_aud_dev_factory *f);
  83. static pj_status_t oboe_get_dev_info(pjmedia_aud_dev_factory *f,
  84. unsigned index,
  85. pjmedia_aud_dev_info *info);
  86. static pj_status_t oboe_default_param(pjmedia_aud_dev_factory *f,
  87. unsigned index,
  88. pjmedia_aud_param *param);
  89. static pj_status_t oboe_create_stream(pjmedia_aud_dev_factory *f,
  90. const pjmedia_aud_param *param,
  91. pjmedia_aud_rec_cb rec_cb,
  92. pjmedia_aud_play_cb play_cb,
  93. void *user_data,
  94. pjmedia_aud_stream **p_aud_strm);
  95. /* Stream prototypes */
  96. static pj_status_t strm_get_param(pjmedia_aud_stream *strm,
  97. pjmedia_aud_param *param);
  98. static pj_status_t strm_get_cap(pjmedia_aud_stream *strm,
  99. pjmedia_aud_dev_cap cap,
  100. void *value);
  101. static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
  102. pjmedia_aud_dev_cap cap,
  103. const void *value);
  104. static pj_status_t strm_start(pjmedia_aud_stream *strm);
  105. static pj_status_t strm_stop(pjmedia_aud_stream *strm);
  106. static pj_status_t strm_destroy(pjmedia_aud_stream *strm);
  107. static pjmedia_aud_dev_factory_op oboe_op =
  108. {
  109. &oboe_init,
  110. &oboe_destroy,
  111. &oboe_get_dev_count,
  112. &oboe_get_dev_info,
  113. &oboe_default_param,
  114. &oboe_create_stream,
  115. &oboe_refresh
  116. };
  117. static pjmedia_aud_stream_op oboe_strm_op =
  118. {
  119. &strm_get_param,
  120. &strm_get_cap,
  121. &strm_set_cap,
  122. &strm_start,
  123. &strm_stop,
  124. &strm_destroy
  125. };
  126. /*
  127. * Init Android Oboe audio driver.
  128. */
  129. #ifdef __cplusplus
  130. extern "C"{
  131. #endif
  132. pjmedia_aud_dev_factory* pjmedia_android_oboe_factory(pj_pool_factory *pf)
  133. {
  134. struct oboe_aud_factory *f;
  135. pj_pool_t *pool;
  136. pool = pj_pool_create(pf, "oboe", 256, 256, NULL);
  137. f = PJ_POOL_ZALLOC_T(pool, struct oboe_aud_factory);
  138. f->pf = pf;
  139. f->pool = pool;
  140. f->base.op = &oboe_op;
  141. f->dev_pool = pj_pool_create(pf, "oboe_dev", 256, 256, NULL);
  142. return &f->base;
  143. }
  144. #ifdef __cplusplus
  145. }
  146. #endif
  147. /* JNI stuff for enumerating audio devices. This will invoke Java code
  148. * in pjmedia/src/pjmedia-audiodev/android/PjAudioDevInfo.java.
  149. */
  150. #define PJ_AUDDEV_INFO_CLASS_PATH "org/pjsip/PjAudioDevInfo"
  151. static struct jni_objs_t
  152. {
  153. struct {
  154. jclass cls;
  155. jmethodID m_get_cnt;
  156. jmethodID m_get_info;
  157. jmethodID m_refresh;
  158. jfieldID f_id;
  159. jfieldID f_name;
  160. jfieldID f_direction;
  161. jfieldID f_sup_clockrates;
  162. jfieldID f_sup_channels;
  163. } dev_info;
  164. struct {
  165. jclass cls;
  166. jmethodID m_current;
  167. jmethodID m_get_app;
  168. } activity_thread;
  169. } jobjs;
  170. #define GET_CLASS(class_path, class_name, cls) \
  171. cls = jni_env->FindClass(class_path); \
  172. if (cls == NULL || jni_env->ExceptionCheck()) { \
  173. jni_env->ExceptionClear(); \
  174. PJ_LOG(3, (THIS_FILE, "[JNI] Unable to find class '" \
  175. class_name "'")); \
  176. status = PJMEDIA_EAUD_SYSERR; \
  177. goto on_return; \
  178. } else { \
  179. jclass tmp = cls; \
  180. cls = (jclass)jni_env->NewGlobalRef(tmp); \
  181. jni_env->DeleteLocalRef(tmp); \
  182. if (cls == NULL) { \
  183. PJ_LOG(3, (THIS_FILE, "[JNI] Unable to get global ref for " \
  184. "class '" class_name "'")); \
  185. status = PJMEDIA_EAUD_SYSERR; \
  186. goto on_return; \
  187. } \
  188. }
  189. #define GET_METHOD_ID(cls, class_name, method_name, signature, id) \
  190. id = jni_env->GetMethodID(cls, method_name, signature); \
  191. if (id == 0) { \
  192. PJ_LOG(3, (THIS_FILE, "[JNI] Unable to find method '" method_name \
  193. "' in class '" class_name "'")); \
  194. status = PJMEDIA_EAUD_SYSERR; \
  195. goto on_return; \
  196. }
  197. #define GET_SMETHOD_ID(cls, class_name, method_name, signature, id) \
  198. id = jni_env->GetStaticMethodID(cls, method_name, signature); \
  199. if (id == 0) { \
  200. PJ_LOG(3, (THIS_FILE, "[JNI] Unable to find static method '" \
  201. method_name "' in class '" class_name "'")); \
  202. status = PJMEDIA_EAUD_SYSERR; \
  203. goto on_return; \
  204. }
  205. #define GET_FIELD_ID(cls, class_name, field_name, signature, id) \
  206. id = jni_env->GetFieldID(cls, field_name, signature); \
  207. if (id == 0) { \
  208. PJ_LOG(3, (THIS_FILE, "[JNI] Unable to find field '" field_name \
  209. "' in class '" class_name "'")); \
  210. status = PJMEDIA_EAUD_SYSERR; \
  211. goto on_return; \
  212. }
  213. /* Get Java object IDs (via FindClass, GetMethodID, GetFieldID, etc).
  214. * Note that this function should be called from library-loader thread,
  215. * otherwise FindClass, etc, may fail, see:
  216. * http://developer.android.com/training/articles/perf-jni.html#faq_FindClass
  217. */
  218. static pj_status_t jni_init_ids()
  219. {
  220. JNIEnv *jni_env;
  221. pj_status_t status = PJ_SUCCESS;
  222. pj_bool_t with_attach = pj_jni_attach_jvm((void **)&jni_env);
  223. /* PjAudioDevInfo class info */
  224. GET_CLASS(PJ_AUDDEV_INFO_CLASS_PATH, "PjAudioDevInfo", jobjs.dev_info.cls);
  225. GET_SMETHOD_ID(jobjs.dev_info.cls, "PjAudioDevInfo", "GetCount", "()I",
  226. jobjs.dev_info.m_get_cnt);
  227. GET_SMETHOD_ID(jobjs.dev_info.cls, "PjAudioDevInfo", "GetInfo",
  228. "(I)L" PJ_AUDDEV_INFO_CLASS_PATH ";",
  229. jobjs.dev_info.m_get_info);
  230. GET_SMETHOD_ID(jobjs.dev_info.cls, "PjAudioDevInfo", "RefreshDevices",
  231. "(Landroid/content/Context;)V",
  232. jobjs.dev_info.m_refresh);
  233. GET_FIELD_ID(jobjs.dev_info.cls, "PjAudioDevInfo", "id", "I",
  234. jobjs.dev_info.f_id);
  235. GET_FIELD_ID(jobjs.dev_info.cls, "PjAudioDevInfo", "name", "Ljava/lang/String;",
  236. jobjs.dev_info.f_name);
  237. GET_FIELD_ID(jobjs.dev_info.cls, "PjAudioDevInfo", "direction", "I",
  238. jobjs.dev_info.f_direction);
  239. GET_FIELD_ID(jobjs.dev_info.cls, "PjAudioDevInfo", "supportedClockRates", "[I",
  240. jobjs.dev_info.f_sup_clockrates);
  241. GET_FIELD_ID(jobjs.dev_info.cls, "PjAudioDevInfo", "supportedChannelCounts", "[I",
  242. jobjs.dev_info.f_sup_channels);
  243. /* ActivityThread class info */
  244. GET_CLASS("android/app/ActivityThread", "ActivityThread",
  245. jobjs.activity_thread.cls);
  246. GET_SMETHOD_ID(jobjs.activity_thread.cls, "ActivityThread",
  247. "currentActivityThread", "()Landroid/app/ActivityThread;",
  248. jobjs.activity_thread.m_current);
  249. GET_METHOD_ID(jobjs.activity_thread.cls, "ActivityThread",
  250. "getApplication", "()Landroid/app/Application;",
  251. jobjs.activity_thread.m_get_app);
  252. on_return:
  253. pj_jni_detach_jvm(with_attach);
  254. return status;
  255. }
  256. #undef GET_CLASS_ID
  257. #undef GET_METHOD_ID
  258. #undef GET_SMETHOD_ID
  259. #undef GET_FIELD_ID
  260. static void jni_deinit_ids()
  261. {
  262. JNIEnv *jni_env;
  263. pj_bool_t with_attach = pj_jni_attach_jvm((void **)&jni_env);
  264. if (jobjs.dev_info.cls) {
  265. jni_env->DeleteGlobalRef(jobjs.dev_info.cls);
  266. jobjs.dev_info.cls = NULL;
  267. }
  268. if (jobjs.activity_thread.cls) {
  269. jni_env->DeleteGlobalRef(jobjs.activity_thread.cls);
  270. jobjs.activity_thread.cls = NULL;
  271. }
  272. pj_jni_detach_jvm(with_attach);
  273. }
  274. static jobject get_global_context(JNIEnv *jni_env)
  275. {
  276. jobject context = NULL;
  277. jobject cur_at = jni_env->CallStaticObjectMethod(
  278. jobjs.activity_thread.cls,
  279. jobjs.activity_thread.m_current);
  280. if (cur_at==NULL)
  281. return NULL;
  282. context = jni_env->CallObjectMethod(cur_at,
  283. jobjs.activity_thread.m_get_app);
  284. return context;
  285. }
  286. /* API: Init factory */
  287. static pj_status_t oboe_init(pjmedia_aud_dev_factory *f)
  288. {
  289. pj_status_t status;
  290. status = jni_init_ids();
  291. if (status != PJ_SUCCESS)
  292. return status;
  293. status = oboe_refresh(f);
  294. if (status != PJ_SUCCESS)
  295. return status;
  296. return PJ_SUCCESS;
  297. }
  298. /* API: refresh the list of devices */
  299. static pj_status_t oboe_refresh(pjmedia_aud_dev_factory *ff)
  300. {
  301. struct oboe_aud_factory *f = (struct oboe_aud_factory*)ff;
  302. JNIEnv *jni_env;
  303. pj_bool_t with_attach;
  304. int i, dev_count = 0;
  305. pj_status_t status = PJ_SUCCESS;
  306. /* Clean up device info and pool */
  307. f->dev_count = 0;
  308. pj_pool_reset(f->dev_pool);
  309. with_attach = pj_jni_attach_jvm((void **)&jni_env);
  310. jobject context = get_global_context(jni_env);
  311. if (context == NULL) {
  312. PJ_LOG(3, (THIS_FILE, "Failed to get context"));
  313. status = PJMEDIA_EAUD_SYSERR;
  314. goto on_return;
  315. }
  316. /* PjAudioDevInfo::RefreshDevices(Context) */
  317. jni_env->CallStaticVoidMethod(jobjs.dev_info.cls,
  318. jobjs.dev_info.m_refresh, context);
  319. /* dev_count = PjAudioDevInfo::GetCount() */
  320. dev_count = jni_env->CallStaticIntMethod(jobjs.dev_info.cls,
  321. jobjs.dev_info.m_get_cnt);
  322. if (dev_count < 0) {
  323. PJ_LOG(3, (THIS_FILE, "Failed to get camera count"));
  324. status = PJMEDIA_EAUD_SYSERR;
  325. goto on_return;
  326. }
  327. /* Start querying device info */
  328. f->dev_info = (aud_dev_info*)
  329. pj_pool_calloc(f->dev_pool, dev_count,
  330. sizeof(aud_dev_info));
  331. for (i = 0; i < dev_count; i++) {
  332. aud_dev_info *adi = &f->dev_info[f->dev_count];
  333. pjmedia_aud_dev_info *base_adi = &adi->info;
  334. jobject jdev_info;
  335. jint jinttmp;
  336. /* jdev_info = PjAudioDevInfo::GetInfo(i) */
  337. jdev_info = jni_env->CallStaticObjectMethod(
  338. jobjs.dev_info.cls,
  339. jobjs.dev_info.m_get_info,
  340. i);
  341. if (jdev_info == NULL)
  342. continue;
  343. /* Get device ID, direction, etc */
  344. adi->id = jni_env->GetIntField(jdev_info, jobjs.dev_info.f_id);
  345. jinttmp = jni_env->GetIntField(jdev_info, jobjs.dev_info.f_direction);
  346. base_adi->input_count = (jinttmp & PJMEDIA_DIR_CAPTURE);
  347. base_adi->output_count = (jinttmp & PJMEDIA_DIR_PLAYBACK);
  348. base_adi->caps = 0;
  349. /* Get name info */
  350. jstring jstrtmp = (jstring)
  351. jni_env->GetObjectField(jdev_info,
  352. jobjs.dev_info.f_name);
  353. const char *strtmp = jni_env->GetStringUTFChars(jstrtmp, NULL);
  354. pj_ansi_strxcpy(base_adi->name, strtmp, sizeof(base_adi->name));
  355. pj_ansi_strxcpy(base_adi->driver, DRIVER_NAME,
  356. sizeof(base_adi->driver));
  357. f->dev_count++;
  358. on_skip_dev:
  359. jni_env->DeleteLocalRef(jdev_info);
  360. }
  361. PJ_LOG(4, (THIS_FILE,
  362. "Oboe audio device initialized with %d device(s):",
  363. f->dev_count));
  364. for (i = 0; i < f->dev_count; i++) {
  365. aud_dev_info *adi = &f->dev_info[i];
  366. PJ_LOG(4, (THIS_FILE, "%2d (native id=%d): %s (%s%s%s)",
  367. i, adi->id, adi->info.name,
  368. adi->info.input_count?"in":"",
  369. adi->info.input_count && adi->info.output_count?"+":"",
  370. adi->info.output_count?"out":""
  371. ));
  372. }
  373. on_return:
  374. if (context)
  375. jni_env->DeleteLocalRef(context);
  376. pj_jni_detach_jvm(with_attach);
  377. return status;
  378. }
  379. /* API: Destroy factory */
  380. static pj_status_t oboe_destroy(pjmedia_aud_dev_factory *f)
  381. {
  382. struct oboe_aud_factory *pa = (struct oboe_aud_factory*)f;
  383. pj_pool_t *pool;
  384. PJ_LOG(4, (THIS_FILE, "Oboe sound library shutting down.."));
  385. jni_deinit_ids();
  386. pool = pa->pool;
  387. pa->pool = NULL;
  388. pj_pool_release(pool);
  389. return PJ_SUCCESS;
  390. }
  391. /* API: Get device count. */
  392. static unsigned oboe_get_dev_count(pjmedia_aud_dev_factory *ff)
  393. {
  394. struct oboe_aud_factory *f = (struct oboe_aud_factory*)ff;
  395. return f->dev_count;
  396. }
  397. /* API: Get device info. */
  398. static pj_status_t oboe_get_dev_info(pjmedia_aud_dev_factory *ff,
  399. unsigned index,
  400. pjmedia_aud_dev_info *info)
  401. {
  402. struct oboe_aud_factory *f = (struct oboe_aud_factory*)ff;
  403. PJ_ASSERT_RETURN(index < f->dev_count, PJMEDIA_EAUD_INVDEV);
  404. pj_memcpy(info, &f->dev_info[index].info, sizeof(*info));
  405. return PJ_SUCCESS;
  406. }
  407. /* API: fill in with default parameter. */
  408. static pj_status_t oboe_default_param(pjmedia_aud_dev_factory *ff,
  409. unsigned index,
  410. pjmedia_aud_param *param)
  411. {
  412. struct oboe_aud_factory *f = (struct oboe_aud_factory*)ff;
  413. pjmedia_aud_dev_info adi;
  414. pj_status_t status;
  415. PJ_ASSERT_RETURN(index < f->dev_count, PJMEDIA_EAUD_INVDEV);
  416. status = oboe_get_dev_info(ff, index, &adi);
  417. if (status != PJ_SUCCESS)
  418. return status;
  419. pj_bzero(param, sizeof(*param));
  420. if (adi.input_count && adi.output_count) {
  421. param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
  422. param->rec_id = index;
  423. param->play_id = index;
  424. } else if (adi.input_count) {
  425. param->dir = PJMEDIA_DIR_CAPTURE;
  426. param->rec_id = index;
  427. param->play_id = PJMEDIA_AUD_INVALID_DEV;
  428. } else if (adi.output_count) {
  429. param->dir = PJMEDIA_DIR_PLAYBACK;
  430. param->play_id = index;
  431. param->rec_id = PJMEDIA_AUD_INVALID_DEV;
  432. } else {
  433. return PJMEDIA_EAUD_INVDEV;
  434. }
  435. param->clock_rate = adi.default_samples_per_sec;
  436. param->channel_count = 1;
  437. param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
  438. param->bits_per_sample = 16;
  439. param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
  440. param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
  441. return PJ_SUCCESS;
  442. }
  443. /* Atomic queue (ring buffer) for single consumer & single producer.
  444. *
  445. * Producer invokes 'put(frame)' to put a frame to the back of the queue and
  446. * consumer invokes 'get(frame)' to get a frame from the head of the queue.
  447. *
  448. * For producer, there is write pointer 'ptrWrite' that will be incremented
  449. * every time a frame is queued to the back of the queue. If the queue is
  450. * almost full (the write pointer is right before the read pointer) the
  451. * producer will forcefully discard the oldest frame in the head of the
  452. * queue by incrementing read pointer.
  453. *
  454. * For consumer, there is read pointer 'ptrRead' that will be incremented
  455. * every time a frame is fetched from the head of the queue, only if the
  456. * pointer is not modified by producer (in case of queue full).
  457. */
  458. class AtomicQueue {
  459. public:
  460. AtomicQueue(unsigned max_frame_cnt, unsigned frame_size,
  461. const char* name_= "") :
  462. maxFrameCnt(max_frame_cnt), frameSize(frame_size),
  463. ptrWrite(0), ptrRead(0),
  464. buffer(NULL), name(name_)
  465. {
  466. buffer = new char[maxFrameCnt * frameSize];
  467. /* Surpress warning when debugging log is disabled */
  468. PJ_UNUSED_ARG(name);
  469. }
  470. ~AtomicQueue() {
  471. delete [] buffer;
  472. }
  473. /* Get a frame from the head of the queue */
  474. bool get(void* frame) {
  475. if (ptrRead == ptrWrite)
  476. return false;
  477. unsigned cur_ptr = ptrRead;
  478. void *p = &buffer[cur_ptr * frameSize];
  479. pj_memcpy(frame, p, frameSize);
  480. inc_ptr_read_if_not_yet(cur_ptr);
  481. //__android_log_print(ANDROID_LOG_INFO, name,
  482. // "GET: ptrRead=%d ptrWrite=%d\n",
  483. // ptrRead.load(), ptrWrite.load());
  484. return true;
  485. }
  486. /* Put a frame to the back of the queue */
  487. void put(void* frame) {
  488. unsigned cur_ptr = ptrWrite;
  489. void *p = &buffer[cur_ptr * frameSize];
  490. pj_memcpy(p, frame, frameSize);
  491. unsigned next_ptr = inc_ptr_write(cur_ptr);
  492. /* Increment read pointer if next write is overlapping (next_ptr == read ptr) */
  493. unsigned next_read_ptr = (next_ptr == maxFrameCnt-1)? 0 : (next_ptr+1);
  494. ptrRead.compare_exchange_strong(next_ptr, next_read_ptr);
  495. //__android_log_print(ANDROID_LOG_INFO, name,
  496. // "PUT: ptrRead=%d ptrWrite=%d\n",
  497. // ptrRead.load(), ptrWrite.load());
  498. }
  499. private:
  500. unsigned maxFrameCnt;
  501. unsigned frameSize;
  502. std::atomic<unsigned> ptrWrite;
  503. std::atomic<unsigned> ptrRead;
  504. char *buffer;
  505. const char *name;
  506. /* Increment read pointer, only if producer not incemented it already.
  507. * Producer may increment the read pointer if the write pointer is
  508. * right before the read pointer (buffer almost full).
  509. */
  510. bool inc_ptr_read_if_not_yet(unsigned old_ptr) {
  511. unsigned new_ptr = (old_ptr == maxFrameCnt-1)? 0 : (old_ptr+1);
  512. return ptrRead.compare_exchange_strong(old_ptr, new_ptr);
  513. }
  514. /* Increment write pointer */
  515. unsigned inc_ptr_write(unsigned old_ptr) {
  516. unsigned new_ptr = (old_ptr == maxFrameCnt-1)? 0 : (old_ptr+1);
  517. if (ptrWrite.compare_exchange_strong(old_ptr, new_ptr))
  518. return new_ptr;
  519. /* Should never happen */
  520. pj_assert(!"There is more than one producer!");
  521. return old_ptr;
  522. }
  523. AtomicQueue() {}
  524. };
  525. /* Interface to Oboe */
  526. class MyOboeEngine : oboe::AudioStreamDataCallback,
  527. oboe::AudioStreamErrorCallback
  528. {
  529. public:
  530. MyOboeEngine(struct oboe_aud_stream *stream_, pjmedia_dir dir_)
  531. : stream(stream_), dir(dir_), oboe_stream(NULL), dir_st(NULL),
  532. thread(NULL), thread_quit(PJ_TRUE), queue(NULL),
  533. err_thread_registered(false), mutex(NULL)
  534. {
  535. pj_assert(dir == PJMEDIA_DIR_CAPTURE || dir == PJMEDIA_DIR_PLAYBACK);
  536. dir_st = (dir == PJMEDIA_DIR_CAPTURE? "capture":"playback");
  537. pj_set_timestamp32(&ts, 0, 0);
  538. }
  539. pj_status_t Start() {
  540. pj_status_t status;
  541. if (!mutex) {
  542. status = pj_mutex_create_recursive(stream->pool, "oboe", &mutex);
  543. if (status != PJ_SUCCESS) {
  544. PJ_PERROR(3,(THIS_FILE, status,
  545. "Oboe stream %s failed creating mutex", dir_st));
  546. return status;
  547. }
  548. }
  549. int dev_id = 0;
  550. oboe::AudioStreamBuilder sb;
  551. pj_mutex_lock(mutex);
  552. if (oboe_stream) {
  553. pj_mutex_unlock(mutex);
  554. return PJ_SUCCESS;
  555. }
  556. if (dir == PJMEDIA_DIR_CAPTURE) {
  557. sb.setDirection(oboe::Direction::Input);
  558. if (stream->param.rec_id >= 0 &&
  559. stream->param.rec_id < stream->f->dev_count)
  560. {
  561. dev_id = stream->f->dev_info[stream->param.rec_id].id;
  562. }
  563. } else {
  564. sb.setDirection(oboe::Direction::Output);
  565. if (stream->param.play_id >= 0 &&
  566. stream->param.play_id < stream->f->dev_count)
  567. {
  568. dev_id = stream->f->dev_info[stream->param.play_id].id;
  569. }
  570. }
  571. sb.setDeviceId(dev_id);
  572. sb.setSampleRate(stream->param.clock_rate);
  573. sb.setChannelCount(stream->param.channel_count);
  574. sb.setPerformanceMode(oboe::PerformanceMode::LowLatency);
  575. sb.setFormat(oboe::AudioFormat::I16);
  576. sb.setUsage(oboe::Usage::VoiceCommunication);
  577. sb.setContentType(oboe::ContentType::Speech);
  578. sb.setDataCallback(this);
  579. sb.setErrorCallback(this);
  580. sb.setFramesPerDataCallback(stream->param.samples_per_frame /
  581. stream->param.channel_count);
  582. /* Somehow mic does not work on Samsung S10 (get no error and
  583. * low latency, but callback is never invoked) if sample rate
  584. * conversion is specified. If it is not specified (default is None),
  585. * mic does not get low latency on, but it works.
  586. */
  587. if (dir == PJMEDIA_DIR_PLAYBACK) {
  588. sb.setSampleRateConversionQuality(
  589. oboe::SampleRateConversionQuality::High);
  590. /* Also if mic is Exclusive, it won't reopen after
  591. * plug/unplug headset (on Samsung S10).
  592. */
  593. sb.setSharingMode(oboe::SharingMode::Exclusive);
  594. }
  595. /* Create queue */
  596. unsigned latency = (dir == PJMEDIA_DIR_CAPTURE?
  597. stream->param.input_latency_ms :
  598. stream->param.output_latency_ms);
  599. unsigned queue_size = latency * stream->param.clock_rate *
  600. stream->param.channel_count / 1000 /
  601. stream->param.samples_per_frame;
  602. /* Normalize queue size to be in range of 3-10 frames */
  603. if (queue_size < 3) queue_size = 3;
  604. if (queue_size > 10) queue_size = 10;
  605. PJ_LOG(3,(THIS_FILE,
  606. "Oboe stream %s queue size=%d frames (latency=%d ms)",
  607. dir_st, queue_size, latency));
  608. queue = new AtomicQueue(queue_size, stream->param.samples_per_frame*2,
  609. dir_st);
  610. /* Create semaphore */
  611. if (sem_init(&sem, 0, 0) != 0) {
  612. pj_mutex_unlock(mutex);
  613. return PJ_RETURN_OS_ERROR(pj_get_native_os_error());
  614. }
  615. /* Create thread */
  616. thread_quit = PJ_FALSE;
  617. status = pj_thread_create(stream->pool, "android_oboe",
  618. &AudioThread, this, 0, 0, &thread);
  619. if (status != PJ_SUCCESS) {
  620. pj_mutex_unlock(mutex);
  621. return status;
  622. }
  623. /* Open & start oboe stream */
  624. oboe::Result result = sb.openStream(&oboe_stream);
  625. if (result != oboe::Result::OK) {
  626. PJ_LOG(3,(THIS_FILE,
  627. "Oboe stream %s open failed (err=%d/%s)",
  628. dir_st, result, oboe::convertToText(result)));
  629. pj_mutex_unlock(mutex);
  630. return PJMEDIA_EAUD_SYSERR;
  631. }
  632. result = oboe_stream->requestStart();
  633. if (result != oboe::Result::OK) {
  634. PJ_LOG(3,(THIS_FILE,
  635. "Oboe stream %s start failed (err=%d/%s)",
  636. dir_st, result, oboe::convertToText(result)));
  637. pj_mutex_unlock(mutex);
  638. return PJMEDIA_EAUD_SYSERR;
  639. }
  640. PJ_LOG(4, (THIS_FILE,
  641. "Oboe stream %s started, "
  642. "id=%d, clock_rate=%d, channel_count=%d, "
  643. "samples_per_frame=%d (%dms), "
  644. "API=%d/%s, exclusive=%s, low latency=%s, "
  645. "size per callback=%d, buffer capacity=%d, burst size=%d",
  646. dir_st,
  647. stream->param.play_id,
  648. stream->param.clock_rate,
  649. stream->param.channel_count,
  650. stream->param.samples_per_frame,
  651. stream->param.samples_per_frame * 1000 /
  652. stream->param.clock_rate,
  653. oboe_stream->getAudioApi(),
  654. (oboe_stream->usesAAudio()? "AAudio":"other"),
  655. (oboe_stream->getSharingMode()==
  656. oboe::SharingMode::Exclusive? "yes":"no"),
  657. (oboe_stream->getPerformanceMode()==
  658. oboe::PerformanceMode::LowLatency? "yes":"no"),
  659. oboe_stream->getFramesPerDataCallback()*
  660. stream->param.channel_count,
  661. oboe_stream->getBufferCapacityInFrames(),
  662. oboe_stream->getFramesPerBurst()
  663. ));
  664. pj_mutex_unlock(mutex);
  665. return PJ_SUCCESS;
  666. }
  667. void Stop() {
  668. /* Just return if it has not been started */
  669. if (!mutex || thread_quit) {
  670. PJ_LOG(5, (THIS_FILE, "Oboe stream %s stop request when "
  671. "already stopped.", dir_st));
  672. return;
  673. }
  674. PJ_LOG(5, (THIS_FILE, "Oboe stream %s stop requested.", dir_st));
  675. pj_mutex_lock(mutex);
  676. if (thread) {
  677. PJ_LOG(5,(THIS_FILE, "Oboe %s stopping thread", dir_st));
  678. thread_quit = PJ_TRUE;
  679. sem_post(&sem);
  680. pj_thread_join(thread);
  681. pj_thread_destroy(thread);
  682. thread = NULL;
  683. }
  684. if (oboe_stream) {
  685. PJ_LOG(5,(THIS_FILE, "Oboe %s closing stream", dir_st));
  686. oboe_stream->close();
  687. delete oboe_stream;
  688. oboe_stream = NULL;
  689. }
  690. if (queue) {
  691. PJ_LOG(5,(THIS_FILE, "Oboe %s deleting queue", dir_st));
  692. delete queue;
  693. queue = NULL;
  694. }
  695. sem_destroy(&sem);
  696. pj_mutex_unlock(mutex);
  697. PJ_LOG(4, (THIS_FILE, "Oboe stream %s stopped.", dir_st));
  698. }
  699. /* Oboe callback, here let's just use Android native mutex & semaphore
  700. * so we don't need to register the thread to PJLIB.
  701. */
  702. oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream,
  703. void *audioData,
  704. int32_t numFrames)
  705. {
  706. if (dir == PJMEDIA_DIR_CAPTURE) {
  707. /* Put the audio frame to queue */
  708. queue->put(audioData);
  709. } else {
  710. /* Get audio frame from queue */
  711. if (!queue->get(audioData)) {
  712. pj_bzero(audioData, stream->param.samples_per_frame*2);
  713. __android_log_write(ANDROID_LOG_WARN, THIS_FILE,
  714. "Oboe playback got an empty queue");
  715. }
  716. }
  717. sem_post(&sem);
  718. return (thread_quit? oboe::DataCallbackResult::Stop :
  719. oboe::DataCallbackResult::Continue);
  720. }
  721. void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result result)
  722. {
  723. __android_log_print(ANDROID_LOG_INFO, THIS_FILE,
  724. "Oboe %s got onErrorAfterClose(%d)",
  725. dir_st, result);
  726. /* Register callback thread */
  727. if (!err_thread_registered || !pj_thread_is_registered())
  728. {
  729. pj_thread_t* tmp_thread;
  730. pj_bzero(err_thread_desc, sizeof(pj_thread_desc));
  731. pj_thread_register("oboe_err_thread", err_thread_desc,
  732. &tmp_thread);
  733. err_thread_registered = true;
  734. }
  735. /* Just try to restart */
  736. pj_mutex_lock(mutex);
  737. /* Make sure stop request has not been made */
  738. if (!thread_quit) {
  739. pj_status_t status;
  740. PJ_LOG(3,(THIS_FILE,
  741. "Oboe stream %s error (%d/%s), "
  742. "trying to restart stream..",
  743. dir_st, result, oboe::convertToText(result)));
  744. Stop();
  745. status = Start();
  746. if (status != PJ_SUCCESS) {
  747. pjmedia_event e;
  748. PJ_PERROR(3,(THIS_FILE, status, "Oboe stream restart failed"));
  749. /* Broadcast Oboe error */
  750. pjmedia_event_init(&e, PJMEDIA_EVENT_AUD_DEV_ERROR, &ts,
  751. &stream->base);
  752. e.data.aud_dev_err.dir = dir;
  753. e.data.aud_dev_err.status = status;
  754. e.data.aud_dev_err.id = (dir == PJMEDIA_DIR_PLAYBACK)?
  755. stream->param.play_id :
  756. stream->param.rec_id;
  757. pjmedia_event_publish(NULL, &stream->base, &e,
  758. PJMEDIA_EVENT_PUBLISH_DEFAULT);
  759. }
  760. }
  761. pj_mutex_unlock(mutex);
  762. }
  763. ~MyOboeEngine() {
  764. /* Oboe should have been stopped before destroying the engine.
  765. * As stopping it here (below) may cause undefined behaviour when
  766. * there is race condition against restart in onErrorAfterClose().
  767. */
  768. pj_assert(thread_quit == PJ_TRUE);
  769. /* Forcefully stopping Oboe anyway */
  770. Stop();
  771. /* Try to trigger context switch in case onErrorAfterClose() is
  772. * waiting for mutex.
  773. */
  774. pj_thread_sleep(1);
  775. if (mutex)
  776. pj_mutex_destroy(mutex);
  777. }
  778. private:
  779. static int AudioThread(void *arg) {
  780. MyOboeEngine *this_ = (MyOboeEngine*)arg;
  781. struct oboe_aud_stream *stream = this_->stream;
  782. pj_int16_t *tmp_buf;
  783. unsigned ts_inc;
  784. pj_status_t status;
  785. /* Try to bump up the thread priority */
  786. enum {
  787. THREAD_PRIORITY_AUDIO = -16,
  788. THREAD_PRIORITY_URGENT_AUDIO = -19
  789. };
  790. status = pj_thread_set_prio(NULL, THREAD_PRIORITY_URGENT_AUDIO);
  791. if (status != PJ_SUCCESS) {
  792. PJ_PERROR(3,(THIS_FILE, status,
  793. "Warning: Oboe %s failed increasing thread priority",
  794. this_->dir_st));
  795. }
  796. tmp_buf = new pj_int16_t[this_->stream->param.samples_per_frame]();
  797. ts_inc = stream->param.samples_per_frame/stream->param.channel_count;
  798. /* Queue a silent frame to playback buffer */
  799. if (this_->dir == PJMEDIA_DIR_PLAYBACK) {
  800. this_->queue->put(tmp_buf);
  801. }
  802. while (1) {
  803. sem_wait(&this_->sem);
  804. if (this_->thread_quit)
  805. break;
  806. if (this_->dir == PJMEDIA_DIR_CAPTURE) {
  807. unsigned cnt = 0;
  808. bool stop_stream = false;
  809. /* Read audio frames from Oboe */
  810. while (this_->queue->get(tmp_buf)) {
  811. /* Send audio frame to app via callback rec_cb() */
  812. pjmedia_frame frame;
  813. frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
  814. frame.size = stream->param.samples_per_frame * 2;
  815. frame.bit_info = 0;
  816. frame.buf = (void *)tmp_buf;
  817. frame.timestamp = this_->ts;
  818. status = (*stream->rec_cb)(stream->user_data, &frame);
  819. if (status != PJ_SUCCESS) {
  820. /* App wants to stop audio dev stream */
  821. stop_stream = true;
  822. break;
  823. }
  824. /* Increment timestamp */
  825. pj_add_timestamp32(&this_->ts, ts_inc);
  826. ++cnt;
  827. }
  828. if (stop_stream)
  829. break;
  830. /* Print log for debugging purpose */
  831. if (cnt == 0) {
  832. PJ_LOG(5,(THIS_FILE, "Oboe %s got an empty queue",
  833. this_->dir_st));
  834. } else if (cnt > 1) {
  835. PJ_LOG(5,(THIS_FILE, "Oboe %s got a burst of %d frames",
  836. this_->dir_st, cnt));
  837. }
  838. } else {
  839. /* Get audio frame from app via callback play_cb() */
  840. pjmedia_frame frame;
  841. frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
  842. frame.size = stream->param.samples_per_frame * 2;
  843. frame.bit_info = 0;
  844. frame.buf = (void *)tmp_buf;
  845. frame.timestamp = this_->ts;
  846. status = (*stream->play_cb)(stream->user_data, &frame);
  847. /* Send audio frame to Oboe */
  848. if (status == PJ_SUCCESS) {
  849. this_->queue->put(tmp_buf);
  850. } else {
  851. /* App wants to stop audio dev stream */
  852. break;
  853. }
  854. /* Increment timestamp */
  855. pj_add_timestamp32(&this_->ts, ts_inc);
  856. }
  857. }
  858. delete [] tmp_buf;
  859. PJ_LOG(5,(THIS_FILE, "Oboe %s thread stopped", this_->dir_st));
  860. return 0;
  861. }
  862. private:
  863. struct oboe_aud_stream *stream;
  864. pjmedia_dir dir;
  865. oboe::AudioStream *oboe_stream;
  866. const char *dir_st;
  867. pj_thread_t *thread;
  868. volatile pj_bool_t thread_quit;
  869. sem_t sem;
  870. AtomicQueue *queue;
  871. pj_timestamp ts;
  872. bool err_thread_registered;
  873. pj_thread_desc err_thread_desc;
  874. pj_mutex_t *mutex;
  875. };
  876. /* API: create stream */
  877. static pj_status_t oboe_create_stream(pjmedia_aud_dev_factory *f,
  878. const pjmedia_aud_param *param,
  879. pjmedia_aud_rec_cb rec_cb,
  880. pjmedia_aud_play_cb play_cb,
  881. void *user_data,
  882. pjmedia_aud_stream **p_aud_strm)
  883. {
  884. struct oboe_aud_factory *pa = (struct oboe_aud_factory*)f;
  885. pj_pool_t *pool;
  886. struct oboe_aud_stream *stream;
  887. pj_status_t status = PJ_SUCCESS;
  888. PJ_ASSERT_RETURN(param->channel_count >= 1 && param->channel_count <= 2,
  889. PJ_EINVAL);
  890. PJ_ASSERT_RETURN(param->bits_per_sample==8 || param->bits_per_sample==16,
  891. PJ_EINVAL);
  892. PJ_ASSERT_RETURN(play_cb && rec_cb && p_aud_strm, PJ_EINVAL);
  893. pool = pj_pool_create(pa->pf, "oboestrm", 1024, 1024, NULL);
  894. if (!pool)
  895. return PJ_ENOMEM;
  896. PJ_LOG(4, (THIS_FILE, "Creating Oboe stream"));
  897. stream = PJ_POOL_ZALLOC_T(pool, struct oboe_aud_stream);
  898. stream->pool = pool;
  899. stream->f = pa;
  900. pj_strdup2_with_null(pool, &stream->name, "Oboe stream");
  901. stream->dir = param->dir;
  902. pj_memcpy(&stream->param, param, sizeof(*param));
  903. stream->user_data = user_data;
  904. stream->rec_cb = rec_cb;
  905. stream->play_cb = play_cb;
  906. if (param->dir & PJMEDIA_DIR_CAPTURE) {
  907. stream->rec_engine = new MyOboeEngine(stream, PJMEDIA_DIR_CAPTURE);
  908. if (!stream->rec_engine) {
  909. status = PJ_ENOMEM;
  910. goto on_error;
  911. }
  912. }
  913. if (stream->dir & PJMEDIA_DIR_PLAYBACK) {
  914. stream->play_engine = new MyOboeEngine(stream, PJMEDIA_DIR_PLAYBACK);
  915. if (!stream->play_engine) {
  916. status = PJ_ENOMEM;
  917. goto on_error;
  918. }
  919. }
  920. /* Done */
  921. stream->base.op = &oboe_strm_op;
  922. *p_aud_strm = &stream->base;
  923. return PJ_SUCCESS;
  924. on_error:
  925. strm_destroy(&stream->base);
  926. return status;
  927. }
  928. /* API: Get stream parameters */
  929. static pj_status_t strm_get_param(pjmedia_aud_stream *s,
  930. pjmedia_aud_param *pi)
  931. {
  932. struct oboe_aud_stream *strm = (struct oboe_aud_stream*)s;
  933. PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
  934. pj_memcpy(pi, &strm->param, sizeof(*pi));
  935. return PJ_SUCCESS;
  936. }
  937. /* API: get capability */
  938. static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
  939. pjmedia_aud_dev_cap cap,
  940. void *pval)
  941. {
  942. struct oboe_aud_stream *strm = (struct oboe_aud_stream*)s;
  943. pj_status_t status = PJMEDIA_EAUD_INVCAP;
  944. PJ_UNUSED_ARG(strm);
  945. PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
  946. return status;
  947. }
  948. /* API: set capability */
  949. static pj_status_t strm_set_cap(pjmedia_aud_stream *s,
  950. pjmedia_aud_dev_cap cap,
  951. const void *value)
  952. {
  953. struct oboe_aud_stream *strm = (struct oboe_aud_stream*)s;
  954. PJ_UNUSED_ARG(strm);
  955. PJ_ASSERT_RETURN(s && value, PJ_EINVAL);
  956. return PJMEDIA_EAUD_INVCAP;
  957. }
  958. /* API: start stream. */
  959. static pj_status_t strm_start(pjmedia_aud_stream *s)
  960. {
  961. struct oboe_aud_stream *stream = (struct oboe_aud_stream*)s;
  962. pj_status_t status;
  963. if (stream->running)
  964. return PJ_SUCCESS;
  965. if (stream->rec_engine) {
  966. status = stream->rec_engine->Start();
  967. if (status != PJ_SUCCESS)
  968. goto on_error;
  969. }
  970. if (stream->play_engine) {
  971. status = stream->play_engine->Start();
  972. if (status != PJ_SUCCESS)
  973. goto on_error;
  974. }
  975. stream->running = PJ_TRUE;
  976. PJ_LOG(4, (THIS_FILE, "Oboe stream started"));
  977. return PJ_SUCCESS;
  978. on_error:
  979. if (stream->rec_engine)
  980. stream->rec_engine->Stop();
  981. if (stream->play_engine)
  982. stream->play_engine->Stop();
  983. PJ_LOG(4, (THIS_FILE, "Failed starting Oboe stream"));
  984. return status;
  985. }
  986. /* API: stop stream. */
  987. static pj_status_t strm_stop(pjmedia_aud_stream *s)
  988. {
  989. struct oboe_aud_stream *stream = (struct oboe_aud_stream*)s;
  990. if (!stream->running)
  991. return PJ_SUCCESS;
  992. stream->running = PJ_FALSE;
  993. if (stream->rec_engine)
  994. stream->rec_engine->Stop();
  995. if (stream->play_engine)
  996. stream->play_engine->Stop();
  997. PJ_LOG(4,(THIS_FILE, "Oboe stream stopped"));
  998. return PJ_SUCCESS;
  999. }
  1000. /* API: destroy stream. */
  1001. static pj_status_t strm_destroy(pjmedia_aud_stream *s)
  1002. {
  1003. struct oboe_aud_stream *stream = (struct oboe_aud_stream*)s;
  1004. PJ_LOG(4,(THIS_FILE, "Destroying Oboe stream..."));
  1005. /* Stop the stream */
  1006. strm_stop(s);
  1007. if (stream->rec_engine) {
  1008. delete stream->rec_engine;
  1009. stream->rec_engine = NULL;
  1010. }
  1011. if (stream->play_engine) {
  1012. delete stream->play_engine;
  1013. stream->play_engine = NULL;
  1014. }
  1015. pj_pool_release(stream->pool);
  1016. PJ_LOG(4, (THIS_FILE, "Oboe stream destroyed"));
  1017. return PJ_SUCCESS;
  1018. }
  1019. #endif /* PJMEDIA_AUDIO_DEV_HAS_OBOE */