提要:
近期一直在做视频通话功能,主要基于pjsip来实现的,将这些过程记录下来,可能对做同类型工作的同学有所帮助!
实现思路,参考pjsip原来设备采集视频、编码并rtp组包发送的思路,再在原有流程中做修改!
主要关键点:
1、摄像头采集完成后已经是已编码的H264/H265的流,不需要再开启pjsip的编码/解码流程;
2、组包发送,H264的FU-A组包、PS封装发送;
首先梳理流程,具体包括下面几个点:
1. 摄像头设备适配(这里可以考虑多个数据源,包括文件、socket或者摄像头的数据源;)
2. 参考android_dev.c 实现一个ov5000_dev.c
/* $Id$ */ /* * Copyright (C) 2015 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "util.h" #include <pjmedia-videodev/videodev_imp.h> #include <pj/assert.h> #include <pj/log.h> #include <pj/math.h> #include <pj/os.h> //#define ARM_LINUX #define VIDEO_USE_SOCKET extern void set_take_ps_packet(int enable_value); int log_printf(int a,...) { return 0; } #define PJMEDIA_VIDEO_DEV_HAS_OV5000 1 #if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ defined(PJMEDIA_VIDEO_DEV_HAS_OV5000) #define THIS_FILE "ov5000_dev.c" /* Default video params */ #define DEFAULT_CLOCK_RATE 90000 #define DEFAULT_WIDTH 352 #define DEFAULT_HEIGHT 288 #define DEFAULT_FPS 15 #define ALIGN16(x) ((((x)+15) >> 4) << 4) /* Define whether we should maintain the aspect ratio when rotating the image. * For more details, please refer to util.h. */ #define MAINTAIN_ASPECT_RATIO PJ_TRUE #define JNIEnv (void *) /* Format map info */ typedef struct ov5000_fmt_map { pjmedia_format_id fmt_id; pj_uint32_t ov5000_fmt_id; } ov5000_fmt_map; /* Format map. * Note: it seems that most of Ov5000 devices don't support I420, while * unfortunately, our converter (libyuv based) only support I420 & RGBA, * so in this case, we'd just pretend that we support I420 and we'll do * the NV21/YV12 -> I420 conversion here. */ static ov5000_fmt_map fmt_map[] = { {PJMEDIA_FORMAT_NV21, 0x00000011}, {PJMEDIA_FORMAT_YV12, 0x32315659}, {PJMEDIA_FORMAT_I420, 0x00000023}, /* YUV_420_888 */ }; /* Device info */ typedef struct ov5000_dev_info { pjmedia_vid_dev_info info; /**< Base info */ unsigned dev_idx; /**< Original dev ID */ pj_bool_t facing; /**< Front/back camera?*/ unsigned sup_size_cnt; /**< # of supp'd size */ pjmedia_rect_size *sup_size; /**< Supported size */ unsigned sup_fps_cnt; /**< # of supp'd FPS */ pjmedia_rect_size *sup_fps; /**< Supported FPS */ pj_bool_t has_yv12; /**< Support YV12? */ pj_bool_t has_nv21; /**< Support NV21? */ pj_bool_t forced_i420; /**< Support I420 with conversion */ } ov5000_dev_info; /* Video factory */ typedef struct ov5000_factory { pjmedia_vid_dev_factory base; /**< Base factory */ pj_pool_t *pool; /**< Memory pool */ pj_pool_factory *pf; /**< Pool factory */ pj_pool_t *dev_pool; /**< Device list pool */ unsigned dev_count; /**< Device count */ ov5000_dev_info *dev_info; /**< Device info list */ } ov5000_factory; /* Video stream. */ typedef struct ov5000_stream { pjmedia_vid_dev_stream base; /**< Base stream */ pjmedia_vid_dev_param param; /**< Settings */ pj_pool_t *pool; /**< Memory pool */ ov5000_factory *factory; /**< Factory */ pjmedia_vid_dev_cb vid_cb; /**< Stream callback */ void *user_data; /**< Application data */ pj_bool_t is_running; /**< Stream running? */ void* jcam; /**< PjCamera instance */ pj_timestamp frame_ts; /**< Current timestamp */ unsigned ts_inc; /**< Timestamp interval*/ unsigned convert_to_i420; /**< Need to convert to I420? 0: no 1: from NV21 2: from YV12 */ /** Capture thread info */ pj_bool_t thread_initialized; pj_thread_desc thread_desc; pj_thread_t *thread; /** NV21/YV12 -> I420 Conversion buffer */ pj_uint8_t *convert_buf; pjmedia_rect_size cam_size; /** Converter to rotate frame */ pjmedia_vid_dev_conv conv; // pj_uint8_t *sps_pps; int sps_pps_p_index; int pps_len; int sps_pps_len; int recive_video_packet_count; /** Frame format param for NV21/YV12 -> I420 conversion */ pjmedia_video_apply_fmt_param vafp; //capture thread. pj_thread_t *ca_thread; } ov5000_stream; /* Prototypes */ static pj_status_t ov5000_factory_init(pjmedia_vid_dev_factory *f); static pj_status_t ov5000_factory_destroy(pjmedia_vid_dev_factory *f); static pj_status_t ov5000_factory_refresh(pjmedia_vid_dev_factory *f); static unsigned ov5000_factory_get_dev_count(pjmedia_vid_dev_factory *f); static pj_status_t ov5000_factory_get_dev_info(pjmedia_vid_dev_factory *f, unsigned index, pjmedia_vid_dev_info *info); static pj_status_t ov5000_factory_default_param(pj_pool_t *pool, pjmedia_vid_dev_factory *f, unsigned index, pjmedia_vid_dev_param *param); static pj_status_t ov5000_factory_create_stream( pjmedia_vid_dev_factory *f, pjmedia_vid_dev_param *param, const pjmedia_vid_dev_cb *cb, void *user_data, pjmedia_vid_dev_stream **p_vid_strm); static pj_status_t ov5000_stream_get_param(pjmedia_vid_dev_stream *strm, pjmedia_vid_dev_param *param); static pj_status_t ov5000_stream_get_cap(pjmedia_vid_dev_stream *strm, pjmedia_vid_dev_cap cap, void *value); static pj_status_t ov5000_stream_set_cap(pjmedia_vid_dev_stream *strm, pjmedia_vid_dev_cap cap, const void *value); static pj_status_t ov5000_stream_start(pjmedia_vid_dev_stream *strm); static pj_status_t ov5000_stream_stop(pjmedia_vid_dev_stream *strm); static pj_status_t ov5000_stream_destroy(pjmedia_vid_dev_stream *strm); static void OnGetFrame2(uint8_t* data, int length, void* user_data); /* Operations */ static pjmedia_vid_dev_factory_op factory_op = { &ov5000_factory_init, &ov5000_factory_destroy, &ov5000_factory_get_dev_count, &ov5000_factory_get_dev_info, &ov5000_factory_default_param, &ov5000_factory_create_stream, &ov5000_factory_refresh }; static pjmedia_vid_dev_stream_op stream_op = { &ov5000_stream_get_param, &ov5000_stream_get_cap, &ov5000_stream_set_cap, &ov5000_stream_start, NULL, NULL, &ov5000_stream_stop, &ov5000_stream_destroy }; #endif /* PJMEDIA_VIDEO_DEV_HAS_Ov5000 */
3. ov5000_dev摄像头采集的数据最终会回调到strm->vid_cb.capture_cb ,这个回调方法是video_port.c中的vidstream_cap_cb方法;
static void copy_frame_to_buffer(pjmedia_vid_port *vp, pjmedia_frame *frame) { if (frame == NULL){ return; } pj_mutex_lock(vp->frm_mutex); #if PJMEDIA_VIDEO_DEV_HAS_OV5000 //PJ_LOG(4, (THIS_FILE, "-1--copy_frame_to_buffer: frame.size:%d, %d len", frame->size, vp->frm_buf->size)); #if 1 if (vp->frm_buf == NULL){ pj_mutex_unlock(vp->frm_mutex); return; } ringput(frame->buf, frame->size, 0); vp->frm_buf->size = frame->size; vp->frm_buf->type = frame->type; vp->frm_buf->timestamp = frame->timestamp; vp->frm_buf->bit_info = frame->bit_info; #else //direct put frame? pjmedia_frame_copy(vp->frm_buf, frame); #endif #endif// pj_mutex_unlock(vp->frm_mutex); }
另外,vid_port中的frm_buf缓存数据太少,数据帧存在明显的跳帧现象,所以参考之前的fifobuffer思路,实现了一个fifobuffer;
//add for ring buffer #if PJMEDIA_VIDEO_DEV_HAS_OV5000 pthread_mutex_t ring_mutex; struct ringbuf { unsigned char *buffer; int frame_type; int size; }; static int addring (int i); static int ringget(struct ringbuf *getinfo); static void ringput(unsigned char *buffer,int size,int encode_type); static void ringfree(); static void ringmalloc(int size); static void ringreset(); #define NMAX 10//30 #define RING_BUFFER_SIZE 145000//50000 static volatile int iput = 0; /* */ static volatile int iget = 0; /* */ static volatile int n = 0; /* */ #define USE_MALLOC_MEM #ifndef USE_MALLOC_MEM static uint8_t mem_buffer[RING_BUFFER_SIZE*NMAX]; #endif static volatile struct ringbuf ringfifo[NMAX]; static volatile int init_flag = 0; static void ringmalloc(int size) { int i; #ifdef USE_MALLOC_MEM // pthread_mutex_init(&ring_mutex, 0); if (init_flag){ return; } for(i =0; i<NMAX; i++) { ringfifo[i].buffer = malloc(size); ringfifo[i].size = 0; ringfifo[i].frame_type = 0; // printf("FIFO INFO:idx:%d,len:%d,ptr:%x\n",i,ringfifo[i].size,(int)(ringfifo[i].buffer)); } init_flag = 1; #else for(i =0; i<NMAX; i++) { ringfifo[i].buffer = &mem_buffer[i*RING_BUFFER_SIZE]; ringfifo[i].size = 0; ringfifo[i].frame_type = 0; // printf("FIFO INFO:idx:%d,len:%d,ptr:%x\n",i,ringfifo[i].size,(int)(ringfifo[i].buffer)); } #endif iput = 0; /* ?隆陇D??o3???娄脤?娄脤隆脌?隆茫隆陇?篓篓????? */ iget = 0; /* ?o3???娄脤?娄脤隆脌?隆茫篓篓?3????? */ n = 0; /* ?隆陇D??o3????D娄脤??a??隆脕篓鹿篓潞y篓垄? */ } /************************************************************************************************** ** ** ** **************************************************************************************************/ static void ringreset() { pthread_mutex_lock(&ring_mutex); iput = 0; /* ?隆陇D??o3???娄脤?娄脤隆脌?隆茫隆陇?篓篓????? */ iget = 0; /* ?o3???娄脤?娄脤隆脌?隆茫篓篓?3????? */ n = 0; /* ?隆陇D??o3????D娄脤??a??隆脕篓鹿篓潞y篓垄? */ pthread_mutex_unlock(&ring_mutex); } /************************************************************************************************** ** ** ** **************************************************************************************************/ static void ringfree(void) { int i; printf("begin free mem\n"); for(i =0; i<NMAX; i++) { // printf("FREE FIFO INFO:idx:%d,len:%d,ptr:%x\n",i,ringfifo[i].size,(int)(ringfifo[i].buffer)); #ifdef USE_MALLOC_MEM free(ringfifo[i].buffer); ringfifo[i].buffer = NULL; #endif//#ifdef USE_MALLOC_MEM ringfifo[i].size = 0; } init_flag = 0; //pthread_mutex_destroy(&ring_mutex); } /************************************************************************************************** ** ** ** **************************************************************************************************/ static int addring(int i) { return (i+1) == NMAX ? 0 : i+1; } /************************************************************************************************** ** ** ** **************************************************************************************************/ static int ringget(struct ringbuf *getinfo) { int Pos; if(n>0) { pthread_mutex_lock(&ring_mutex); Pos = iget; iget = addring(iget); n--; getinfo->buffer = (ringfifo[Pos].buffer); if (getinfo->buffer == NULL){ pthread_mutex_unlock(&ring_mutex); return 0; } getinfo->frame_type = ringfifo[Pos].frame_type; getinfo->size = ringfifo[Pos].size; pthread_mutex_unlock(&ring_mutex); //printf("Get FIFO INFO:idx:%d,len:%d,ptr:%x,type:%d\n",Pos,getinfo->size,(int)(getinfo->buffer),getinfo->frame_type); return ringfifo[Pos].size; } else { //printf("Buffer is empty\n"); return 0; } } /************************************************************************************************** ** ** ** **************************************************************************************************/ static void ringput(unsigned char *buffer,int size,int encode_type) { if (size > RING_BUFFER_SIZE){ PJ_PERROR(4,(THIS_FILE, 0, "Error ringput, size:%d > %d", size, RING_BUFFER_SIZE)); return; } if(n >= 0 && n<NMAX) { pthread_mutex_lock(&ring_mutex); if (ringfifo[iput].buffer == NULL){ pthread_mutex_unlock(&ring_mutex); return; } if (size > RING_BUFFER_SIZE){ //pthread_mutex_unlock(&ring_mutex); //return; ringfifo[iput].buffer = realloc(ringfifo[iput].buffer, size); } memcpy(ringfifo[iput].buffer,buffer,size); ringfifo[iput].size= size; ringfifo[iput].frame_type = encode_type; //printf("Put FIFO INFO:idx:%d,len:%d,ptr:%x,type:%d\n",iput,ringfifo[iput].size,(int)(ringfifo[iput].buffer),ringfifo[iput].frame_type); iput = addring(iput); pthread_mutex_unlock(&ring_mutex); n++; } else { // printf("Buffer is full\n"); } } #endif //add end. vid_port.c中的get_frame_from_buffer做如下修改: static pj_status_t get_frame_from_buffer(pjmedia_vid_port *vp, pjmedia_frame *frame) { pj_status_t status = PJ_SUCCESS; pj_mutex_lock(vp->frm_mutex); if (vp->conv.conv) status = convert_frame(vp, vp->frm_buf, frame); else{ //for bug //PJ_LOG(4, (THIS_FILE, "-1--get_frame_from_buffer: frame.size:%d, %d len", frame->size, vp->frm_buf->size)); #if PJMEDIA_VIDEO_DEV_HAS_OV5000 struct ringbuf ringitem ; int itemlen = ringget(&ringitem); if (itemlen > 0 && frame->buf != NULL){ int copy_len = itemlen; if (itemlen > frame->size){ copy_len = frame->size; } memcpy(frame->buf, ringitem.buffer, copy_len); frame->size = copy_len; }else{ frame->size = 0; pj_mutex_unlock(vp->frm_mutex); return -1; } if (vp->frm_buf != NULL){ frame->type = vp->frm_buf->type; frame->timestamp = vp->frm_buf->timestamp; frame->bit_info = vp->frm_buf->bit_info; //PJ_LOG(4, (THIS_FILE, "-2--get_frame_from_buffer: frame.size:%d, %d len, itemlen:%d", frame->size, vp->frm_buf->size, itemlen)); } #else int itemlen = vp->frm_buf->size; pjmedia_frame_copy(frame, vp->frm_buf); #endif } pj_mutex_unlock(vp->frm_mutex); return status; }
3. 视频帧获取驱动定时器适配,调试发现视频流卡顿明显,发现是驱动的定时器跑的太慢,涉及到vid_conf.c中的on_clock_tick方法。
clock_param.clock_rate = 900000;//TS_CLOCK_RATE; clock_param.usec_interval = 1000000 /1000;// vid_conf->opt.frame_rate; status = pjmedia_clock_create2(pool, &clock_param, 0, &on_clock_tick, vid_conf, &vid_conf->clock); #if PJMEDIA_VIDEO_DEV_HAS_OV5000//lyz@ no need converter vp->conv.conv_param.src.id = vp->conv.conv_param.dst.id; vp->conv.conv_param.src.det.vid.size.w = vp->conv.conv_param.dst.det.vid.size.w; vp->conv.conv_param.src.det.vid.size.h= vp->conv.conv_param.dst.det.vid.size.h; //vp->role = ROLE_ACTIVE; //return PJ_SUCCESS; #endif vconf_port结构体中增加 pj_size_t get_buf_real_size; /**< Data size for get_frame(). */ pj_size_t put_buf_real_size; /**< Data size for put_frame(). */ on_clock_tick方法中 status = pjmedia_port_get_frame(src->port, &frame); if (status != PJ_SUCCESS) { PJ_PERROR(5, (THIS_FILE, status, "Failed to get frame from port %d [%s]!", src->idx, src->port->info.name.ptr)); src->got_frame = PJ_FALSE; } else { #if PJMEDIA_VIDEO_DEV_HAS_OV5000//just set got_frame by //PJ_PERROR(4, (THIS_FILE, status, "get frame from port %d ,len:%d, src_buf_size:%d!", src->idx, frame.size, src->get_buf_size)); src->got_frame = PJ_TRUE; src->get_buf_real_size = frame.size; #else src->got_frame = (frame.size == src->get_buf_size); #endif /* There is a possibility that the source port's format has * changed, but we haven't received the event yet. */ cur_fmt = &src->format; new_fmt = &src->port->info.fmt; if (cmp_fps(cur_fmt, new_fmt) || cmp_size(cur_fmt, new_fmt)) { op_param prm; prm.update_port.port = src->idx; op_update_port(vid_conf, &prm); } } render_src_frame方法中做下面的修改 static pj_status_t render_src_frame(vconf_port *src, vconf_port *sink, unsigned transmitter_idx) if (sink->transmitter_cnt == 1 && (!rs || !rs->converter)) { /* The only transmitter and no conversion needed */ #if PJMEDIA_VIDEO_DEV_HAS_OV5000//just set got_frame int get_buf_size = src->get_buf_real_size < sink->put_buf_size?src->get_buf_real_size:sink->put_buf_size; sink->put_buf_real_size = get_buf_size; #else /* The only transmitter and no conversion needed */ if (src->get_buf_size != sink->put_buf_size) return PJMEDIA_EVID_BADFORMAT; int get_buf_size = src->put_buf_size; #endif// pj_memcpy(sink->put_buf, src->get_buf, get_buf_size); } else if (rs && rs->converter) {
4、rtp h264 fu-a组包发送和回调,涉及的文件,vid_stream.c 的 put_frame 方法。
/* mark need modify later. */ #if PJMEDIA_VIDEO_DEV_HAS_OV5000 int rtp_per_packet_len = 1200;//1300; int i=0; int send_len = 0; int reserved_len = frame->size; int data_start_index = 0; #if 1//SUPPORT_PS_ENPACKED char ps_header[PS_HDR_LEN]; char ps_system_header[SYS_HDR_LEN]; char ps_map_header[PSM_HDR_LEN]; char pes_header[PES_HDR_LEN]; char temp_frame[1024 * 128]; #endif// uint8_t nalu = 0; uint8_t *data = (uint8_t *)frame->buf; if ( *data == 0x00 && *(data+1)==0x00 && *(data+2) == 0x00 && *(data+3) == 0x01){ nalu = *(data+4); data_start_index = 4; if (reserved_len > rtp_per_packet_len){ //fu-a data_start_index = 5; } }else if ( *data == 0x00 && *(data+1)==0x00 && *(data+2) == 0x01 ){ nalu = *(data+3); data_start_index = 3; if (reserved_len > rtp_per_packet_len){ //fu-a data_start_index = 4; } }else{ nalu = *(data); data_start_index = 0; } int index = 0; if (ps_packet_flag){ int time_base = 90000; int fps = 24; int send_packet_interval = 1000 / fps; int interval = time_base / fps; stream->pts += interval; long pts = stream->pts; //ps封装 if (nalu == 0x67 || nalu == 0x68 || nalu == 0x65){ //I frame gb28181_make_ps_header(ps_header, pts); memcpy(temp_frame,ps_header,PS_HDR_LEN); index += PS_HDR_LEN; gb28181_make_sys_header(ps_system_header, 0x3f); memcpy(temp_frame+ index, ps_system_header, SYS_HDR_LEN); index += SYS_HDR_LEN; gb28181_make_psm_header(ps_map_header); memcpy(temp_frame + index, ps_map_header, PSM_HDR_LEN); index += PSM_HDR_LEN; }else{ gb28181_make_ps_header(ps_header, pts); memcpy(temp_frame, ps_header, PS_HDR_LEN); index += PS_HDR_LEN; } //封装pes gb28181_make_pes_header(pes_header, 0xe0, reserved_len, pts, pts); memcpy(temp_frame+index, pes_header, PES_HDR_LEN); index += PES_HDR_LEN; memcpy(temp_frame + index, data, reserved_len); index += reserved_len; data = temp_frame; reserved_len = index; data_start_index = 0; }else{ //data_start_index = 0; reserved_len -= data_start_index; } while(1){ send_len = rtp_per_packet_len; if (reserved_len < rtp_per_packet_len){ send_len = reserved_len; has_more_data = PJ_FALSE; }else{ has_more_data = PJ_TRUE; } status = pjmedia_rtp_encode_rtp(&channel->rtp, channel->pt, (has_more_data == PJ_FALSE ? 1 : 0), (int)send_len, rtp_ts_len, (const void**)&rtphdr, &rtphdrlen); if (status != PJ_SUCCESS) { LOGERR_((channel->port.info.name.ptr, status, "RTP encode_rtp() error")); return status; } /* When the payload length is zero, we should not send anything, * but proceed the rest normally. */ int fu_a_index = 0; uint8_t *p_data = (uint8_t *)channel->buf; if (reserved_len > 0) { #if 1 if (frame->size > rtp_per_packet_len){ //fu-a if (total_sent == 0){ //start p_data[sizeof(pjmedia_rtp_hdr)] = (nalu & 0x60) | 28; // |S|E|R| Type | //S 1 E 0 R 0 p_data[sizeof(pjmedia_rtp_hdr)+1] = (1 << 7) | (nalu & 0x1f); fu_a_index += 2; }else{ if (has_more_data){ //end p_data[sizeof(pjmedia_rtp_hdr)] = 28; // |S|E|R| Type | //S 0 E 0 R 0 p_data[sizeof(pjmedia_rtp_hdr)+1] = (nalu & 0x1f); fu_a_index += 2; }else{ //end p_data[sizeof(pjmedia_rtp_hdr)] = 28; // |S|E|R| Type | //S 0 E 1 R 0 p_data[sizeof(pjmedia_rtp_hdr)+1] = (1 << 6) | (nalu & 0x1f); fu_a_index += 2; } } //send_len+=fu_a_index; } #endif//no -fu-a /* Copy RTP header to the beginning of packet */ pj_memcpy(channel->buf, rtphdr, sizeof(pjmedia_rtp_hdr)); //copy data pj_memcpy(channel->buf + fu_a_index + sizeof(pjmedia_rtp_hdr), data +total_sent + data_start_index, send_len+fu_a_index); if (stream->transport == NULL){ break; } /* Send the RTP packet to the transport. */ status = pjmedia_transport_send_rtp(stream->transport, (char*)channel->buf, send_len + sizeof(pjmedia_rtp_hdr) +fu_a_index); if (status != PJ_SUCCESS) { if (stream->rtp_tx_err_cnt++ == 0) { LOGERR_((channel->port.info.name.ptr, status, "Error sending RTP")); } if (stream->rtp_tx_err_cnt > SEND_ERR_COUNT_TO_REPORT) { stream->rtp_tx_err_cnt = 0; } break; } pjmedia_rtcp_tx_rtp(&stream->rtcp, (unsigned)send_len); //total_sent += frame_out.size; pj_thread_sleep(2);//2ms pkt_cnt++; } /* Next packets use same timestamp */ rtp_ts_len = 0; reserved_len -= send_len; total_sent += send_len; if (reserved_len <= 0){ break; } } //PJ_PERROR(4,(THIS_FILE, status, "put_frame len:%d,total_sent:%d", frame->size,total_sent)); goto ov5000_end; #endif ov5000_end: #if TRACE_RC /* Trace log for rate control */ { pj_timestamp end_time; unsigned total_sleep; pj_get_timestamp(&end_time); total_sleep = pj_elapsed_msec(&initial_time, &end_time); PJ_LOG(5, (stream->name.ptr, "total pkt=%d size=%d sleep=%d", pkt_cnt, total_sent, total_sleep)); if (stream->tx_start.u64 == 0) stream->tx_start = initial_time; stream->tx_end = end_time; stream->rc_total_pkt += pkt_cnt; stream->rc_total_sleep += total_sleep; stream->rc_total_img++; } #endif
其他修改:
vid_port_destroy #if PJMEDIA_VIDEO_DEV_HAS_OV5000 //free ringbuffer ringfree(); //add end. #endif
-------------------广告线---------------
项目、合作,欢迎勾搭,邮箱:promall@qq.com
本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com