提要:
近期一直在做视频通话功能,主要基于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
