summaryrefslogtreecommitdiff
path: root/v4l2_vdin.cpp (plain)
blob: 007373d4a233ed7479c80e36bef304c2bf47d513
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18//reinclude because of a bug with the log macros
19//#define LOG_NDEBUG 0
20#define LOG_TAG "V4L2VINSOURCE"
21#include <utils/Log.h>
22#include <utils/String8.h>
23
24#include <signal.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <fcntl.h>
29#include <unistd.h>
30#include <errno.h>
31#include <sys/ioctl.h>
32#include <sys/mman.h>
33#include <sys/select.h>
34#include <linux/videodev.h>
35#include <sys/time.h>
36
37#include <cutils/properties.h>
38#include <sys/types.h>
39#include <sys/stat.h>
40
41#include "v4l2_vdin.h"
42#include <ui/GraphicBufferMapper.h>
43#include <ui/GraphicBuffer.h>
44#include <linux/videodev2.h>
45
46namespace android {
47
48#define V4L2_ROTATE_ID 0x980922
49
50#ifndef container_of
51#define container_of(ptr, type, member) ({ \
52 const typeof(((type *) 0)->member) *__mptr = (ptr); \
53 (type *) ((char *) __mptr - (char *)(&((type *)0)->member)); })
54#endif
55
56#define BOUNDRY 32
57
58#define ALIGN(x) (x + (BOUNDRY) - 1)& ~((BOUNDRY) - 1)
59
60static size_t getBufSize(int format, int width, int height)
61{
62 size_t buf_size = 0;
63
64 switch(format){
65 case V4L2_PIX_FMT_YVU420:
66 case V4L2_PIX_FMT_NV21:
67 buf_size = width * height * 3 / 2;
68 break;
69 case V4L2_PIX_FMT_YUYV:
70 case V4L2_PIX_FMT_RGB565:
71 buf_size = width * height * 2;
72 break;
73 case V4L2_PIX_FMT_RGB24:
74 buf_size = width * height * 3;
75 break;
76 case V4L2_PIX_FMT_RGB32:
77 buf_size = width * height * 4;
78 break;
79 default:
80 ALOGE("Invalid format");
81 buf_size = 0;
82 }
83 return buf_size;
84}
85
86static int getNativeWindowFormat(int format)
87{
88 int nativeFormat = HAL_PIXEL_FORMAT_YCbCr_422_I;
89
90 switch(format){
91 case V4L2_PIX_FMT_YVU420:
92 nativeFormat = HAL_PIXEL_FORMAT_YV12;
93 break;
94 case V4L2_PIX_FMT_NV21:
95 nativeFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP;
96 break;
97 case V4L2_PIX_FMT_YUYV:
98 nativeFormat = HAL_PIXEL_FORMAT_YCbCr_422_I;
99 break;
100 case V4L2_PIX_FMT_RGB565:
101 nativeFormat = HAL_PIXEL_FORMAT_RGB_565;
102 break;
103 case V4L2_PIX_FMT_RGB24:
104 nativeFormat = HAL_PIXEL_FORMAT_RGB_888;
105 break;
106 case V4L2_PIX_FMT_RGB32:
107 nativeFormat = HAL_PIXEL_FORMAT_RGBA_8888;
108 break;
109 default:
110 ALOGE("Invalid format,Use default format");
111 }
112 return nativeFormat;
113}
114
115
116static ANativeWindowBuffer* handle_to_buffer(buffer_handle_t *handle)
117{
118 return container_of(handle, ANativeWindowBuffer, handle);
119}
120
121vdin_screen_source::vdin_screen_source()
122 : mCameraHandle(-1),
123 mVideoInfo(NULL)
124{
125 mCameraHandle = open("/dev/video11", O_RDWR| O_NONBLOCK);
126 if (mCameraHandle < 0){
127 ALOGE("[%s %d] mCameraHandle:%x", __FUNCTION__, __LINE__, mCameraHandle);
128 return -1;
129 }
130 mVideoInfo = (struct VideoInfo *) calloc (1, sizeof (struct VideoInfo));
131 if (mVideoInfo == NULL){
132 ALOGE("[%s %d] no memory for mVideoInfo", __FUNCTION__, __LINE__);
133 close(mCameraHandle);
134 return NO_MEMORY;
135 }
136 mBufferCount = 4;
137 mPixelFormat = V4L2_PIX_FMT_NV21;
138 mNativeWindowPixelFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP;
139 mFrameWidth = 1280;
140 mFrameHeight = 720;
141 mBufferSize = mFrameWidth * mFrameHeight * 3/2;
142 mSetStateCB = NULL;
143 mState = STOP;
144 mANativeWindow = NULL;
145 mFrameType = 0;
146 mWorkThread = NULL;
147 mDataCB = NULL;
148 mOpen = false;
149}
150
151vdin_screen_source::~vdin_screen_source()
152{
153 if (mVideoInfo)
154 free (mVideoInfo);
155 if (mCameraHandle >= 0)
156 close(mCameraHandle);
157}
158
159int vdin_screen_source::start_v4l2_device()
160{
161 int ret = -1;
162
163 ALOGV("[%s %d] mCameraHandle:%x", __FUNCTION__, __LINE__, mCameraHandle);
164
165 ioctl(mCameraHandle, VIDIOC_QUERYCAP, &mVideoInfo->cap);
166
167 mVideoInfo->rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
168 mVideoInfo->rb.memory = V4L2_MEMORY_MMAP;
169 mVideoInfo->rb.count = mBufferCount;
170
171 ret = ioctl(mCameraHandle, VIDIOC_REQBUFS, &mVideoInfo->rb);
172
173 if (ret < 0) {
174 ALOGE("[%s %d] VIDIOC_REQBUFS:%d mCameraHandle:%x", __FUNCTION__, __LINE__, ret, mCameraHandle);
175 return ret;
176 }
177
178 for (int i = 0; i < mBufferCount; i++) {
179 memset (&mVideoInfo->buf, 0, sizeof (struct v4l2_buffer));
180
181 mVideoInfo->buf.index = i;
182 mVideoInfo->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
183 mVideoInfo->buf.memory = V4L2_MEMORY_MMAP;
184
185 ret = ioctl (mCameraHandle, VIDIOC_QUERYBUF, &mVideoInfo->buf);
186 if (ret < 0) {
187 ALOGE("[%s %d]VIDIOC_QUERYBUF %d failed", __FUNCTION__, __LINE__, i);
188 return ret;
189 }
190 mVideoInfo->canvas[i] = mVideoInfo->buf.reserved;
191 mVideoInfo->mem[i] = mmap (0, mVideoInfo->buf.length, PROT_READ | PROT_WRITE,
192 MAP_SHARED, mCameraHandle, mVideoInfo->buf.m.offset);
193
194 if (mVideoInfo->mem[i] == MAP_FAILED) {
195 ALOGE("[%s %d] MAP_FAILED", __FUNCTION__, __LINE__);
196 return -1;
197 }
198 mVideoInfo->refcount[i] = 0;
199 mBufs.add((int)mVideoInfo->mem[i],i);
200 }
201 ALOGV("[%s %d] VIDIOC_QUERYBUF successful", __FUNCTION__, __LINE__);
202
203 for (int i = 0; i < mBufferCount; i++) {
204 mVideoInfo->buf.index = i;
205 mVideoInfo->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
206 mVideoInfo->buf.memory = V4L2_MEMORY_MMAP;
207 ret = ioctl(mCameraHandle, VIDIOC_QBUF, &mVideoInfo->buf);
208 if (ret < 0) {
209 ALOGE("VIDIOC_QBUF Failed");
210 return -1;
211 }
212 }
213 enum v4l2_buf_type bufType;
214 bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
215
216 ret = ioctl (mCameraHandle, VIDIOC_STREAMON, &bufType);
217
218 ALOGV("[%s %d] VIDIOC_STREAMON:%x", __FUNCTION__, __LINE__, ret);
219 return ret;
220}
221
222int vdin_screen_source::start()
223{
224 ALOGV("%s %d", __FUNCTION__, __LINE__);
225 int ret;
226 if(mOpen == true){
227 ALOGI("already open");
228 return NO_ERROR;
229 }
230
231 ret = start_v4l2_device();
232 if(ret != NO_ERROR){
233 ALOGE("Start v4l2 device failed:%d",ret);
234 return ret;
235 }
236 if(mFrameType & NATIVE_WINDOW_DATA){
237 ret = init_native_window();
238 if(ret != NO_ERROR){
239 ALOGE("Init Native Window Failed:%d",ret);
240 return ret;
241 }
242 }
243 if(mFrameType & NATIVE_WINDOW_DATA || mFrameType & CALL_BACK_DATA){
244 ALOGD("Create Work Thread");
245 mWorkThread = new WorkThread(this);
246 }
247 if(mSetStateCB != NULL)
248 mSetStateCB(START);
249 mState = START;
250 mOpen = true;
251 ALOGV("%s %d ret:%d", __FUNCTION__, __LINE__, ret);
252 return NO_ERROR;
253}
254
255int vdin_screen_source::pause()
256{
257 ALOGV("%s %d", __FUNCTION__, __LINE__);
258 mState = PAUSE;
259 if(mSetStateCB != NULL)
260 mSetStateCB(PAUSE);
261 return NO_ERROR;
262}
263int vdin_screen_source::stop()
264{
265 ALOGV("%s %d", __FUNCTION__, __LINE__);
266 int ret;
267 mState = STOPING;
268
269 if(mWorkThread != NULL){
270 mWorkThread->requestExitAndWait();
271 mWorkThread.clear();
272 }
273
274 enum v4l2_buf_type bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
275
276 ret = ioctl (mCameraHandle, VIDIOC_STREAMOFF, &bufType);
277 if (ret < 0) {
278 ALOGE("StopStreaming: Unable to stop capture: %s", strerror(errno));
279 }
280 for (int i = 0; i < mBufferCount; i++){
281 if (munmap(mVideoInfo->mem[i], mVideoInfo->buf.length) < 0)
282 ALOGE("Unmap failed");
283 }
284
285 mBufferCount = 0;
286 mState = STOP;
287 if(mSetStateCB != NULL)
288 mSetStateCB(STOP);
289 mOpen = false;
290 return ret;
291}
292
293int vdin_screen_source::set_state_callback(olStateCB callback)
294{
295 if (!callback){
296 ALOGE("NULL state callback pointer");
297 return BAD_VALUE;
298 }
299 mSetStateCB = callback;
300 return NO_ERROR;
301}
302
303int vdin_screen_source::set_preview_window(ANativeWindow* window)
304{
305 ALOGV("%s %d", __FUNCTION__, __LINE__);
306 if(mOpen == true)
307 return NO_ERROR;
308 //can work without a valid window object ?
309 if (window == NULL){
310 ALOGD("NULL window object passed to ScreenSource");
311 if(mWorkThread != NULL){
312 mWorkThread->requestExitAndWait();
313 mWorkThread.clear();
314 }
315 mFrameType &= ~NATIVE_WINDOW_DATA;
316 return NO_ERROR;
317 }
318 mFrameType |= NATIVE_WINDOW_DATA;
319 mANativeWindow = window;
320 return NO_ERROR;
321}
322
323int vdin_screen_source::set_data_callback(app_data_callback callback, void* user)
324{
325 ALOGV("%s %d", __FUNCTION__, __LINE__);
326 if (callback == NULL){
327 ALOGE("NULL data callback pointer");
328 return BAD_VALUE;
329 }
330 mDataCB = callback;
331 mUser = user;
332 mFrameType |= CALL_BACK_DATA;
333 return NO_ERROR;
334}
335
336int vdin_screen_source::get_format()
337{
338 return mPixelFormat;
339}
340
341int vdin_screen_source::set_format(int width, int height, int color_format)
342{
343 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
344 if(mOpen == true)
345 return NO_ERROR;
346 int ret;
347 mVideoInfo->width = ALIGN(width);
348 mVideoInfo->height = height;
349 mVideoInfo->framesizeIn = (mVideoInfo->width * mVideoInfo->height << 3); //note color format
350 mVideoInfo->formatIn = color_format;
351
352 mVideoInfo->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
353 mVideoInfo->format.fmt.pix.width = ALIGN(width);
354 mVideoInfo->format.fmt.pix.height = height;
355 mVideoInfo->format.fmt.pix.pixelformat = color_format;
356 mPixelFormat = color_format;
357 mNativeWindowPixelFormat = getNativeWindowFormat(color_format);
358 mFrameWidth = ALIGN(width);
359 mFrameHeight = height;
360 mBufferSize = getBufSize(color_format, mFrameWidth, mFrameHeight);
361 ALOGD("mFrameWidth:%d,mFrameHeight:%d",mFrameWidth,mFrameHeight);
362 ALOGD("mPixelFormat:%x,mNativeWindowPixelFormat:%x,mBufferSize:%d",mPixelFormat,mNativeWindowPixelFormat,mBufferSize);
363 ret = ioctl(mCameraHandle, VIDIOC_S_FMT, &mVideoInfo->format);
364 if (ret < 0) {
365 ALOGE("[%s %d]VIDIOC_S_FMT %d", __FUNCTION__, __LINE__, ret);
366 return ret;
367 }
368 return ret;
369}
370
371int vdin_screen_source::set_rotation(int degree)
372{
373 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
374
375 int ret = 0;
376 struct v4l2_control ctl;
377
378 if(mCameraHandle<0)
379 return -1;
380
381 if((degree!=0)&&(degree!=90)&&(degree!=180)&&(degree!=270)){
382 ALOGE("Set rotate value invalid: %d.", degree);
383 return -1;
384 }
385
386 memset( &ctl, 0, sizeof(ctl));
387 ctl.value=degree;
388 ctl.id = V4L2_ROTATE_ID;
389 ret = ioctl(mCameraHandle, VIDIOC_S_CTRL, &ctl);
390
391 if(ret<0){
392 ALOGE("Set rotate value fail: %s. ret=%d", strerror(errno),ret);
393 }
394 return ret ;
395}
396
397int vdin_screen_source::set_crop(int x, int y, int width, int height)
398{
399 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
400 if (NULL == mANativeWindow.get())
401 return BAD_VALUE;
402
403 int err = NO_ERROR;
404 android_native_rect_t crop = { x, y, x + width - 1, y + height - 1 };
405 err = native_window_set_crop(mANativeWindow.get(), &crop);
406 if (err != 0) {
407 ALOGW("Failed to set crop!");
408 return err;
409 }
410 return NO_ERROR;
411}
412
413int vdin_screen_source::set_frame_rate(int frameRate)
414{
415 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
416 int ret = 0;
417 struct v4l2_control ctl;
418
419 if(mCameraHandle<0)
420 return -1;
421
422 struct v4l2_streamparm sparm;
423 memset(&sparm, 0, sizeof( sparm ));
424 sparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//stream_flag;
425 sparm.parm.output.timeperframe.denominator = frameRate;
426 sparm.parm.output.timeperframe.numerator = 1;
427
428 ret = ioctl(mCameraHandle, VIDIOC_S_PARM, &sparm);
429 if(ret < 0){
430 ALOGE("Set frame rate fail: %s. ret=%d", strerror(errno),ret);
431 }
432 return ret ;
433}
434
435int vdin_screen_source::set_source_type(int sourceType)
436{
437 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
438 int ret = 0;
439
440 ret = ioctl(mCameraHandle, VIDIOC_S_INPUT, &sourceType);
441 if(ret < 0){
442 ALOGE("Set source type fail: %s. ret:%d", strerror(errno),ret);
443 }
444 return ret;
445}
446
447int vdin_screen_source::get_source_type()
448{
449 ALOGV("[%s %d]", __FUNCTION__, __LINE__);
450 int ret = -1;
451 int sourceType;
452
453 ret = ioctl(mCameraHandle, VIDIOC_G_INPUT, &sourceType);
454 if(ret < 0){
455 ALOGE("Set source type fail: %s. ret:%d", strerror(errno),ret);
456 return ret;
457 }
458 return sourceType;
459}
460
461int vdin_screen_source::aquire_buffer(int *buff_info)
462{
463 ALOGV("%s %d", __FUNCTION__, __LINE__);
464 int ret = -1;
465 mVideoInfo->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
466 mVideoInfo->buf.memory = V4L2_MEMORY_MMAP;
467
468 ret = ioctl(mCameraHandle, VIDIOC_DQBUF, &mVideoInfo->buf);
469 if (ret < 0) {
470 if(EAGAIN == errno){
471 ret = -EAGAIN;
472 }else{
473 ALOGE("[%s %d]aquire_buffer %d", __FUNCTION__, __LINE__, ret);
474 }
475 buff_info[0] = 0;
476 buff_info[1] = 0;
477 return ret;
478 }
479 buff_info[0] = (unsigned)mVideoInfo->mem[mVideoInfo->buf.index];
480 buff_info[1] = (unsigned)mVideoInfo->canvas[mVideoInfo->buf.index];
481 buff_info[2] = mVideoInfo->buf.timestamp.tv_sec;
482 buff_info[3] = mVideoInfo->buf.timestamp.tv_usec;
483 return ret;
484}
485
486/* int vdin_screen_source::inc_buffer_refcount(int *ptr){
487 ALOGV("%s %d", __FUNCTION__, __LINE__);
488 int ret = -1;
489 int index;
490 index = mBufs.valueFor((unsigned int)ptr);
491 mVideoInfo->refcount[index] += 1;
492 return true;
493} */
494
495int vdin_screen_source::release_buffer(int* ptr)
496{
497 ALOGV("%s %d", __FUNCTION__, __LINE__);
498 int ret = -1;
499 int currentIndex;
500 v4l2_buffer hbuf_query;
501
502 Mutex::Autolock autoLock(mLock);
503
504 currentIndex = mBufs.valueFor((unsigned int)ptr);
505 if(mVideoInfo->refcount[currentIndex] > 0){
506 mVideoInfo->refcount[currentIndex] -= 1;
507 }else{
508 ALOGE("return buffer when refcount already zero");
509 return 0;
510 }
511 if(mVideoInfo->refcount[currentIndex] == 0){
512 memset(&hbuf_query,0,sizeof(v4l2_buffer));
513 hbuf_query.index = currentIndex;
514 hbuf_query.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
515 hbuf_query.memory = V4L2_MEMORY_MMAP;
516 ALOGV("return buffer :%d",currentIndex);
517 ret = ioctl(mCameraHandle, VIDIOC_QBUF, &hbuf_query);
518 if(ret != 0){
519 ALOGE("Return Buffer :%d failed", currentIndex);
520 }
521 }
522 return 0;
523}
524
525int vdin_screen_source::init_native_window()
526{
527 ALOGV("%s %d", __FUNCTION__, __LINE__);
528 int err = NO_ERROR;
529
530 if(NULL == mANativeWindow.get())
531 return BAD_VALUE;
532
533 // Set gralloc usage bits for window.
534 err = native_window_set_usage(mANativeWindow.get(), SCREENSOURCE_GRALLOC_USAGE);
535 if (err != 0) {
536 ALOGE("native_window_set_usage failed: %s\n", strerror(-err));
537 if(ENODEV == err ){
538 ALOGE("Preview surface abandoned!");
539 mANativeWindow = NULL;
540 }
541 return err;
542 }
543
544 ALOGD("Number of buffers set to ANativeWindow %d", mBufferCount);
545 ///Set the number of buffers needed for camera preview
546 err = native_window_set_buffer_count(mANativeWindow.get(), mBufferCount);
547 if (err != 0) {
548 ALOGE("native_window_set_buffer_count failed: %s (%d)", strerror(-err), -err);
549 if(ENODEV == err){
550 ALOGE("Preview surface abandoned!");
551 mANativeWindow = NULL;
552 }
553 return err;
554 }
555
556 ALOGD("native_window_set_buffers_geometry format:0x%x",mNativeWindowPixelFormat);
557 // Set window geometry
558 err = native_window_set_buffers_geometry(
559 mANativeWindow.get(),
560 mFrameWidth,
561 mFrameHeight,
562 mNativeWindowPixelFormat);
563
564 if (err != 0) {
565 ALOGE("native_window_set_buffers_geometry failed: %s", strerror(-err));
566 if ( ENODEV == err ) {
567 ALOGE("Surface abandoned!");
568 mANativeWindow = NULL;
569 }
570 return err;
571 }
572 err = native_window_set_scaling_mode(mANativeWindow.get(), NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
573 if (err != 0) {
574 ALOGW("Failed to set scaling mode: %d", err);
575 return err;
576 }
577 return NO_ERROR;
578}
579
580int vdin_screen_source::workThread()
581{
582 bool buff_keep = false;
583 int index;
584 int buff_info[4], buff_info_latest[4];
585 int ret;
586 unsigned char *src = NULL;
587 unsigned char *dest = NULL;
588 uint8_t *handle = NULL;
589 ANativeWindowBuffer* buf;
590 if(mState == START){
591 usleep(5000);
592#if 0
593 ret = aquire_buffer(buff_info);
594#else
595 while(mState == START){
596 ret = aquire_buffer(buff_info);
597 if(ret != 0){
598 if(true == buff_keep){//use the latest buffer
599 memcpy(buff_info, buff_info_latest, 16);
600 ret = 0;
601 }
602 break;
603 }else{
604 if(true == buff_keep){
605 //release
606 ALOGD("v4l2 vdin drop frame");
607 release_buffer((int *)(buff_info_latest[0]));
608 }
609 buff_keep = true;
610 memcpy(buff_info_latest, buff_info, 16);
611 }
612 }
613#endif
614 if(ret != 0 || (buff_info[0] == 0)){
615 ALOGV("Get V4l2 buffer failed");
616 return ret;
617 }
618 src = (unsigned char *)buff_info[0];
619 index = mBufs.valueFor((unsigned int)src);
620 if(mFrameType & NATIVE_WINDOW_DATA){
621 mVideoInfo->refcount[index] += 1;
622 if(mANativeWindow.get() == NULL){
623 ALOGE("Null window");
624 return BAD_VALUE;
625 }
626 ret = mANativeWindow->dequeueBuffer_DEPRECATED(mANativeWindow.get(), &buf);
627 if(ret != 0){
628 ALOGE("dequeue buffer failed :%s (%d)",strerror(-ret), -ret);
629 return BAD_VALUE;
630 }
631 mANativeWindow->lockBuffer_DEPRECATED(mANativeWindow.get(), buf);
632 sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(buf, false));
633 graphicBuffer->lock(SCREENSOURCE_GRALLOC_USAGE, (void **)&dest);
634 if(dest == NULL){
635 ALOGE("Invalid Gralloc Handle");
636 return BAD_VALUE;
637 }
638 memcpy(dest, src, mBufferSize);
639 graphicBuffer->unlock();
640 mANativeWindow->queueBuffer_DEPRECATED(mANativeWindow.get(), buf);
641 graphicBuffer.clear();
642 ALOGV("queue one buffer to native window");
643 release_buffer((int*)src);
644 }
645 if(mFrameType & CALL_BACK_DATA && mDataCB != NULL&& mState == START){
646 mVideoInfo->refcount[index] += 1;
647 mDataCB(mUser, buff_info);
648 }
649 }
650 return NO_ERROR;
651}
652
653}
654