树莓派sip视频电话-4:使用高清摄像头

很久没有更新树莓派上的sip视频电话程序了,最近入手了csi接口的摄像头,就完善一下程序.

可以配合freeswitch使用,可以实现视频会议功能.

主要问题:没有实现主动呼叫功能.

更新:1.使用csi 摄像头传输高清视频;2.实现音频播放;3.完善其他功能;4.使用了omxcam库(github上有)

程序没有做优化,有很多重复代码,主要是为了实现功能,有时间再优化.


1.audio_rtp_recv.c

#define ALSA_PCM_NEW_HW_PARAMS_API
#include <fcntl.h>
#include <unistd.h>
#include <alsa/asoundlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "audio_rtp_recv.h"
#include "g711codec.h"

snd_pcm_t *handle;

int audio_recv_init(){
	int err;
    if ((err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
        printf("Playback open error: %s/n", snd_strerror(err));
        exit(1);
    }
    if ((err = snd_pcm_set_params(handle,
                    SND_PCM_FORMAT_S16_LE,//SND_PCM_FORMAT_U8,
                    SND_PCM_ACCESS_RW_INTERLEAVED,/* snd_pcm_readi/snd_pcm_writei access */
                    1, //Channels
                    8000, //sample rate in Hz
                    1, //soft_resample
                    500000)) < 0) {如果latency过小,会使得snd_pcm_writei()丢发声数据,产生short write现象 1000000=1sec
        printf("Playback open error: %s/n", snd_strerror(err));
        exit(1);
    }
	return 0;
}

int audio_recv_close(){
	snd_pcm_drain(handle);
	snd_pcm_close(handle);
	return 0;
}


void *audio_recv(void *AudioParam){
	int rc;
	struct audio_param_recv *audiorecvparam=AudioParam;
	char recvbuffer[256];
    char outbuffer[320];
	int recv_len;
	int frames=160;//注意定义alsa中frames
	
	audio_recv_init();
  while (audiorecvparam->recv_quit==1) {
	  	bzero(recvbuffer, sizeof(recvbuffer));
	  	usleep(100); //防止cpu过高
        recv_len = recv(audiorecvparam->audio_rtp_socket, recvbuffer, sizeof(recvbuffer), 0 );
        if(recv_len<0) 	continue;
        //printf("audio recv_len=%d,seq=%d\n",recv_len,recvbuffer[2] << 8|recvbuffer[3] << 0);
        rc=G711u2PCM(&recvbuffer[12], outbuffer, 160, 0);//应该返回值为320
		if(rc<0)  fprintf(stderr,RED "[%s]:" NONE "G711u2PCM error:rc=%d\n",__FILE__,rc);
		
		//送到pcm解码播放
		rc = snd_pcm_writei(handle, outbuffer, frames);
		if (rc == -EPIPE) {
		fprintf(stderr, RED "[%s]:" NONE "underrun occurred\n",__FILE__);
		snd_pcm_prepare(handle);
		} else if (rc < 0) {
		fprintf(stderr,RED "[%s]:" NONE "error from writei: %s\n",__FILE__,snd_strerror(rc));
		} else if (rc != (int)frames) {
		fprintf(stderr,RED "[%s]:" NONE "short write, write %d frames,not %d\n",__FILE__, rc,frames);
		}
  }
	audio_recv_close();
	close(audiorecvparam->audio_rtp_socket);
	return 0;
}

/*
int fd = open ("recv.pcm", O_WRONLY | O_CREAT | O_TRUNC | O_APPEND, 0666);//保存解码接收后的声音文件
if (pwrite (fd, outbuffer, rc, 0) == -1) fprintf (stderr, "error: pwrite\n");//写入到文件用于测试
close (fd);//关闭句柄
*/

2.audio_rtp_recv.h

#define NONE                      "\033[m"  
#define RED                         "\033[1;31m"
#define GREEN                   "\033[1;32m" 
#define BLUE                       "\033[1;34m"

typedef struct audio_param_recv
{
int    audio_rtp_socket;
char *audio_hw;
char *dest_ip ;
int      dest_port;
int      local_port;
int         recv_quit;
} audio_param_recv;
void *audio_recv(void *AudioParam) ;
3.audio_rtp_send.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>

#include <netdb.h>
#include <time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h> 

#include <alsa/asoundlib.h>
#include <math.h>
#include "g711codec.h"
#include "audio_rtp_send.h"

#define BUFFERSIZE 4096
#define PERIOD_SIZE 1024
#define PERIODS 2
#define SAMPLE_RATE 8000
#define CHANNELS 1
#define FSIZE 2*CHANNELS

#define ALSA_PCM_NEW_HW_PARAMS_API

void *audio_send(void *AudioSendParam) 
{
	struct audio_param_send *audiosendparam=AudioSendParam;
	//char *audio_hw, char *dest_ip, int dest_port
	
	fprintf(stderr,GREEN"[%s]:"NONE"param:audio_hw=%s,dest_ip=%s,dest_port=%d\n",__FILE__,audiosendparam->audio_hw,audiosendparam->dest_ip,audiosendparam->dest_port);

	int rc;	//return code.
	int size;
	snd_pcm_t *handle;
	snd_pcm_hw_params_t *params;
	unsigned int val;
	int dir;
	snd_pcm_uframes_t frames;
	char *buffer;
 	int err;

	/* Open PCM device for recording (capture). */
	err = snd_pcm_open(&handle, audiosendparam->audio_hw , SND_PCM_STREAM_CAPTURE, 0);
	if (err < 0) {
		fprintf(stderr,RED "[%s@%s,%d]:" NONE "unable to open pcm device: %s\n",__func__, __FILE__, __LINE__,snd_strerror(err));
		exit(1);
	}

	/* Allocate a hardware parameters object. */
	snd_pcm_hw_params_alloca(¶ms);

	/* Fill it in with default values. */
	err=snd_pcm_hw_params_any(handle, params);
		if (err < 0) {
		    fprintf(stderr, RED "[%s@%s,%d]:" NONE "Can not configure this PCM device: %s\n",__func__, __FILE__, __LINE__,snd_strerror(err));
		    exit(1);
		}
	/* Set the desired hardware parameters. */

	/* Interleaved mode */
	err=snd_pcm_hw_params_set_access(handle, params,SND_PCM_ACCESS_RW_INTERLEAVED);
		if (err < 0) {
		    fprintf(stderr,  RED   "[%s@%s,%d]:" NONE "Failed to set PCM device to interleaved: %s\n", __func__, __FILE__, __LINE__,snd_strerror(err));
		    exit(1);
		}
	/* Signed 16-bit little-endian format */
	//err=snd_pcm_hw_params_set_format(handle, params,SND_PCM_FORMAT_MU_LAW);
	err=snd_pcm_hw_params_set_format(handle, params,SND_PCM_FORMAT_S16_LE);
		if (err < 0) {
		    fprintf(stderr,RED  "[%s@%s,%d]:" NONE "Failed to set PCM device to 16-bit signed PCM: %s\n", __func__, __FILE__, __LINE__,snd_strerror(err));
		    exit(1);
		}
	/* One channels (mono) */
	err=snd_pcm_hw_params_set_channels(handle, params, CHANNELS);
		if (err < 0) {
		    fprintf(stderr,RED "[%s@%s,%d]:" NONE "Failed to set PCM device to mono: %s\n",__func__, __FILE__, __LINE__,snd_strerror(err));
		    exit(1);
		}
	/* 8000 bits/second sampling rate (CD quality) */
	val = 8000;//这里修改为8000
	//val = 44100; 
	err=snd_pcm_hw_params_set_rate_near(handle, params,&val, &dir);
		if (err < 0) {
		    fprintf(stderr, RED "[%s@%s,%d]:" NONE "Failed to set PCM device to sample rate =%d: %s\n",__func__, __FILE__, __LINE__,val,snd_strerror(err));
		    exit(1);
		}
	/* Set period size to 32 frames. */
	


	// Set buffer time 500000.
	
	unsigned int buffer_time,period_time;
	snd_pcm_hw_params_get_buffer_time_max(params, &buffer_time, 0);
	if ( buffer_time >500000) 		
	buffer_time = 80000;//这里可修改
	period_time = buffer_time/4;//这里可修改  size = frames * FSIZE;
	err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, 0);
		if (err < 0) {
		    fprintf(stderr, RED "[%s@%s,%d]:" NONE "Failed to set PCM device to buffer time =%d: %s\n",  __func__, __FILE__, __LINE__,buffer_time,snd_strerror(err));
		    exit(1);
		}

	err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, 0);
		if (err < 0) {
		    fprintf(stderr, RED  "[%s@%s,%d]:" NONE "Failed to set PCM device to period time =%d: %s\n",__func__, __FILE__, __LINE__,period_time,snd_strerror(err));
		    exit(1);
		}



	err = snd_pcm_hw_params(handle, params);
		if (err < 0) {
			fprintf(stderr,RED  "[%s@%s,%d]:" NONE "unable to set hw parameters: %s\n",__func__, __FILE__, __LINE__,snd_strerror(err));
			exit(1);
		}

	/* Use a buffer large enough to hold one period */
	snd_pcm_hw_params_get_period_size(params,&frames, &dir);
	size = frames * FSIZE; /* 2 bytes/sample, 1 channels *///这里应该=320  FSIZE=2    frames=160
	buffer = (char *) malloc(size);
	
	fprintf(stderr,GREEN "[%s@%s]:" NONE "read buffer size = %d\n",__func__, __FILE__,size);
	fprintf(stderr,GREEN "[%s@%s]:" NONE "period size = %d frames\n",__func__, __FILE__,(int)frames);


	/*print alsa config parameter*/
	snd_pcm_hw_params_get_period_time(params,&val, &dir);
	fprintf(stderr,GREEN "[%s@%s]:" NONE "period time is: %d\n",__func__, __FILE__,val);
	snd_pcm_hw_params_get_buffer_time(params, &val, &dir);
	fprintf(stderr,GREEN "[%s@%s]:" NONE "buffer time = %d us\n",__func__, __FILE__,val);
	snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &val);
	fprintf(stderr,GREEN "[%s@%s]:" NONE "buffer size = %d frames\n",__func__, __FILE__,val);
	snd_pcm_hw_params_get_periods(params, &val, &dir);
	fprintf(stderr,GREEN "[%s@%s]:" NONE "periods per buffer = %d frames\n",__func__, __FILE__,val);

    int M_bit=1;

    char sendbuf[1500];
    memset(sendbuf,0,1500);
    unsigned short seq_num = 0;
    RTP_FIXED_HEADER        *rtp_hdr;

	unsigned int timestamp_increse = 0,ts_current = 0;
	timestamp_increse = 160;
	while (audiosendparam->send_quit==1) {
		//1.采集
		rc = snd_pcm_readi(handle, buffer, frames);//采集音频数据
		if (rc == -EPIPE) {
			fprintf(stderr, RED "[%s@%s,%d]:overrun occurred\n",__func__, __FILE__, __LINE__);
			err=snd_pcm_prepare(handle);
			if( err <0){
				fprintf(stderr, RED  "[%s@%s,%d]:Failed to recover form overrun : %s\n",__func__, __FILE__, __LINE__,
				snd_strerror(err));
				exit(1);
			}
		}
		else if (rc < 0) {
			fprintf(stderr,RED "[%s@%s,%d]:" NONE "error from read: %s\n",__func__, __FILE__, __LINE__,snd_strerror(rc));
			exit(1);
		} 
		else if (rc != (int)frames) {
			fprintf(stderr, RED  "[%s@%s,%d]:" NONE "short read, read %d frames\n", __func__, __FILE__, __LINE__,rc);
		}
		
		//2.编码
		rc = PCM2G711u( (char *)buffer, (char *)&sendbuf[12], size, 0 );//pcm转g711a
		if(rc<0)  fprintf(stderr,RED "[%s@%s,%d]:" NONE "PCM2G711u error:rc=%d\n",__func__, __FILE__, __LINE__,rc);
		

		//3.打包
		rtp_hdr =(RTP_FIXED_HEADER*)&sendbuf[0];
		rtp_hdr->payload     = 0;  //负载类型号,
		rtp_hdr->version     = 2;  //版本号,此版本固定为2
		if(1 == M_bit)	{
			rtp_hdr->marker    = 1;   //标志位,由具体协议规定其值。
			M_bit = 0;
		}
		else{
			rtp_hdr->marker    = 0;   //标志位,由具体协议规定其值。
		}
		rtp_hdr->ssrc        = htonl(10);    //随机指定为10,并且在本RTP会话中全局唯一
		rtp_hdr->seq_no = htons(seq_num ++);//rtp包序号
		ts_current = ts_current+timestamp_increse;
		rtp_hdr->timestamp=htonl(ts_current);//rtp传输时间戳,增量为timestamp_increse=160
		
		//4.发送
		rc = send( audiosendparam->audio_rtp_socket, sendbuf, rc+12, 0 );//开始发送rtp包,+12是rtp的包头+g711荷载
		
		if(rc<0) {
			//对方呼叫结束产生错误
			//fprintf(stderr , RED "[%s@%s,%d]:" NONE "net send error=%d\n", __func__, __FILE__, __LINE__,rc);
			break;
		}
        memset(sendbuf,0,1500);//清空sendbuf;此时会将上次的时间戳清空,因此需要ts_current来保存上次的时间戳值

	}
	//shutdown(rtp_socket,2);
	close(audiosendparam->audio_rtp_socket);
	snd_pcm_drain(handle);
	snd_pcm_close(handle);
	free(buffer);
	return NULL;
}

4.audio_rtp_send.h

#define NONE                      "\033[m"  
#define RED                         "\033[1;31m"
#define GREEN                   "\033[1;32m" 
#define BLUE                       "\033[1;34m"

typedef struct
{
    /** byte 0 */
    unsigned char csrc_len:4;        /** expect 0 */
    unsigned char extension:1;       /** expect 1, see RTP_OP below */
    unsigned char padding:1;         /** expect 0 */
    unsigned char version:2;         /** expect 2 */
    /** byte 1 */
    unsigned char payload:7;         /** stream type */
    unsigned char marker:1;          /** when send the first framer,set it */
    /** bytes 2, 3 */
    unsigned short seq_no;
    /** bytes 4-7 */
    unsigned  long timestamp;
    /** bytes 8-11 */
    unsigned long ssrc;              /** stream number is used here. */
} RTP_FIXED_HEADER;


typedef struct audio_param_send
{
int    audio_rtp_socket;
char *audio_hw;
char *dest_ip ;
int      dest_port;
int      local_port;
int         recv_quit;
int         send_quit;
} audio_param_send;

void *audio_send(void *AudioSendParam) ;

5.common.h

#define NONE                      "\033[m"  
#define RED                         "\033[1;31m"
#define GREEN                   "\033[1;32m" 
#define BLUE                       "\033[1;34m"

#define OMX_INIT_STRUCTURE(a) \
  memset(&(a), 0, sizeof(a)); \
  (a).nSize = sizeof(a); \
  (a).nVersion.s.nVersionMajor = OMX_VERSION_MAJOR; \
  (a).nVersion.s.nVersionMinor = OMX_VERSION_MINOR; \
  (a).nVersion.s.nRevision = OMX_VERSION_REVISION; \
  (a).nVersion.s.nStep = OMX_VERSION_STEP

#define BOOL int
#define TRUE 1
#define FALSE 0
typedef struct rect
{
int      x1;
int      y1;
int       x2;
int       y2;	
}rect;
typedef struct rtp_param
{
char *dest_ip ;
int      dest_port;
int      local_port;
int       thread_exit;
} rtp_param;
typedef struct dec_param
{
int       alpha;
struct  rect videorect;
int       thread_exit;
int        m_settings_changed;
} dec_param;



static inline OMX_TICKS ToOMXTime(int64_t pts)
{
  OMX_TICKS ticks;
  ticks.nLowPart = pts;
  ticks.nHighPart = pts >> 32;
  return ticks;
}
static inline int64_t FromOMXTime(OMX_TICKS ticks)
{
  int64_t pts = ticks.nLowPart | ((uint64_t)(ticks.nHighPart) << 32);
  return pts;
}

6.g711.c

#include <stdio.h>
#include "g711codec.h"

/*
 * function: convert PCM audio format to g711 alaw/ulaw.(zqj)
 *	 InAudioData:	PCM data prepared for encoding to g711 alaw/ulaw.
 *   OutAudioData:	encoded g711 alaw/ulaw.
 *   DataLen:		PCM data size.
 *   reserve:		reserved param, no use.
 */

/*alaw*/
int PCM2G711a( char *InAudioData, char *OutAudioData, int DataLen, int reserve )
{	
	//check params.
	if( (NULL == InAudioData) && (NULL == OutAudioData) && (0 == DataLen) )
	{
		printf("Error, empty data or transmit failed, exit !\n");	
		return -1;
	}
	//printf("DataLen = %d, %s, %d\n", DataLen, __func__, __LINE__);

	int Retaen = 0; 
	//printf("G711a encode start......\n");
	Retaen = g711a_encode( (unsigned char *)OutAudioData, (short*)InAudioData, DataLen/2 );
	//printf("Retaen = %d, %s, %d\n", Retaen, __func__, __LINE__);

	return Retaen; //index successfully encoded data len.
}

/*ulaw*/
int PCM2G711u( char *InAudioData, char *OutAudioData, int DataLen, int reserve )
{	
	//check params.
	if( (NULL == InAudioData) && (NULL == OutAudioData) && (0 == DataLen) )
	{
		printf("Error, empty data or transmit failed, exit !\n");	
		return -1;
	}
	//printf("DataLen = %d, %s, %d\n", DataLen, __func__, __LINE__);

	int Retuen = 0; 
	//printf("G711u encode start......\n");
	Retuen = g711u_encode( (unsigned char *)OutAudioData, (short*)InAudioData, DataLen/2 );
	//printf("Retuen = %d, %s, %d\n", Retuen, __func__, __LINE__);

	return Retuen; 
}

/*
 * function: convert g711 alaw audio format to PCM.(zqj)
 *	 InAudioData:	g711 alaw data prepared for encoding to PCM.
 *   OutAudioData:	encoded PCM audio data.
 *   DataLen:		g711a data size.
 *   reserve:		reserved param, no use.
 */

/*alaw*/
int G711a2PCM( char *InAudioData, char *OutAudioData, int DataLen, int reserve )
{
	//check param.
	if( (NULL == InAudioData) && (NULL == OutAudioData) && (0 == DataLen) )
	{
		printf("Error, empty data or transmit failed, exit !\n");	
		return -1;
	}
	printf("DataLen = %d, %s, %d\n", DataLen, __func__, __LINE__);

	int Retade = 0;
	printf("G711a decode start......\n");
	Retade = g711a_decode( (short*)OutAudioData, (unsigned char *)InAudioData, DataLen );
	printf("Retade = %d, %s, %d\n", Retade, __func__, __LINE__);

	return Retade;	//index successfully decoded data len.
}

/*ulaw*/
int G711u2PCM( char *InAudioData, char *OutAudioData, int DataLen, int reserve )
{
	//check param.
	if( (NULL == InAudioData) && (NULL == OutAudioData) && (0 == DataLen) )
	{
		printf("Error, empty data or transmit failed, exit !\n");	
		return -1;
	}
	//printf("DataLen = %d, %s, %d\n", DataLen, __func__, __LINE__);

	int Retude = 0;
	//printf("G711u decode start......\n");
	Retude = g711u_decode( (short*)OutAudioData, (unsigned char *)InAudioData, DataLen );
	//printf("Retude = %d, %s, %d\n", Retude, __func__, __LINE__);

	return Retude;	
}

7.g711codec.c

#include "g711codec.h"

static short seg_end[8] = {0xFF, 0x1FF, 0x3FF, 0x7FF,
			    0xFFF, 0x1FFF, 0x3FFF, 0x7FFF};

static int search(int val, short	*table, int	size)
{
	int	i;

	for (i = 0; i < size; i++) {
		if (val <= *table++)
			return (i);
	}
	return (size);
}

/*
* alaw2linear() - Convert an A-law value to 16-bit linear PCM
*
*/
static int alaw2linear( unsigned char a_val )
{
	int	t;
	int	seg;

	a_val ^= 0x55;

	t = (a_val & QUANT_MASK) << 4;
	seg = ( (unsigned)a_val & SEG_MASK ) >> SEG_SHIFT;
	switch (seg) 
	{
		case 0:
			t += 8;
			break;
		case 1:
			t += 0x108;
			break;
		default:
			t += 0x108;
			t <<= seg - 1;
	}
	return ((a_val & SIGN_BIT) ? t : -t);
}


/*
* ulaw2linear() - Convert a u-law value to 16-bit linear PCM
*
* First, a biased linear code is derived from the code word. An unbiased
* output can then be obtained by subtracting 33 from the biased code.
*
* Note that this function expects to be passed the complement of the
* original code word. This is in keeping with ISDN conventions.
*/
static int ulaw2linear(unsigned char u_val)
{
	int	t;

	/* Complement to obtain normal u-law value. */
	u_val = ~u_val;

	/*
	* Extract and bias the quantization bits. Then
	* shift up by the segment number and subtract out the bias.
	*/
	t = ((u_val & QUANT_MASK) << 3) + BIAS;
	t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;

	return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
}


/*
 * linear2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
 *
 */
unsigned char linear2alaw(int pcm_val)	/* 2's complement (16-bit range) */
{
	int		mask;
	int		seg;
	unsigned char	aval;

	if (pcm_val >= 0) {
		mask = 0xD5;		/* sign (7th) bit = 1 */
	} else {
		mask = 0x55;		/* sign bit = 0 */
		pcm_val = -pcm_val - 8;
	}

	/* Convert the scaled magnitude to segment number. */
	seg = search(pcm_val, seg_end, 8);

	/* Combine the sign, segment, and quantization bits. */

	if (seg >= 8)		/* out of range, return maximum value. */
		return (0x7F ^ mask);
	else {
		aval = seg << SEG_SHIFT;
		if (seg < 2)
			aval |= (pcm_val >> 4) & QUANT_MASK;
		else
			aval |= (pcm_val >> (seg + 3)) & QUANT_MASK;
		return (aval ^ mask);
	}
}


/*
 * linear2ulaw() - Convert a linear PCM value to u-law
 *
 */
unsigned char linear2ulaw(int pcm_val)	/* 2's complement (16-bit range) */
{
	int		mask;
	int		seg;
	unsigned char	uval;

	/* Get the sign and the magnitude of the value. */
	if (pcm_val < 0) {
		pcm_val = BIAS - pcm_val;
		mask = 0x7F;
	} else {
		pcm_val += BIAS;
		mask = 0xFF;
	}

	/* Convert the scaled magnitude to segment number. */
	seg = search(pcm_val, seg_end, 8);

	/*
	 * Combine the sign, segment, quantization bits;
	 * and complement the code word.
	 */
	if (seg >= 8)		/* out of range, return maximum value. */
		return (0x7F ^ mask);
	else {
		uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF);
		return (uval ^ mask);
	}
}


int g711a_decode( short amp[], const unsigned char g711a_data[], int g711a_bytes )
{
	int i;
	int samples;
	unsigned char code;
	int sl;

	for ( samples = i = 0; ; )
	{
		if (i >= g711a_bytes)
			break;
		code = g711a_data[i++];

		sl = alaw2linear( code );

		amp[samples++] = (short) sl;
	}
	return samples*2;
}

int g711u_decode(short amp[], const unsigned char g711u_data[], int g711u_bytes)
{
	int i;
	int samples;
	unsigned char code;
	int sl;

	for (samples = i = 0;;)
	{
		if (i >= g711u_bytes)
			break;
		code = g711u_data[i++];

		sl = ulaw2linear(code);

		amp[samples++] = (short) sl;
	}
	return samples*2;
}

int g711a_encode(unsigned char g711_data[], const short amp[], int len)
{
    int i;

    for (i = 0;  i < len;  i++)
	{
        g711_data[i] = linear2alaw(amp[i]);
    }

    return len;
}

int g711u_encode(unsigned char g711_data[], const short amp[], int len)
{
    int i;

    for (i = 0;  i < len;  i++)
	{
        g711_data[i] = linear2ulaw(amp[i]);
    }

    return len;
}

8.g711codec.h

/*
 * G711 encode decode HEADER.
 */

#ifndef	__G711CODEC_H__
#define	__G711CODEC_H__

/*
* u-law, A-law and linear PCM conversions.
*/
#define	SIGN_BIT	(0x80)		/* Sign bit for a A-law byte. */
#define	QUANT_MASK	(0xf)		/* Quantization field mask. */
#define	NSEGS		(8)			/* Number of A-law segments. */
#define	SEG_SHIFT	(4)			/* Left shift for segment number. */
#define	SEG_MASK	(0x70)		/* Segment field mask. */
#define	BIAS		(0x84)		/* Bias for linear code. */

int PCM2G711a( char *InAudioData, char *OutAudioData, int DataLen, int reserve );
int PCM2G711u( char *InAudioData, char *OutAudioData, int DataLen, int reserve );

int G711a2PCM( char *InAudioData, char *OutAudioData, int DataLen, int reserve );
int G711u2PCM( char *InAudioData, char *OutAudioData, int DataLen, int reserve );

int g711a_decode(short amp[], const unsigned char g711a_data[], int g711a_bytes);

int g711u_decode(short amp[], const unsigned char g711u_data[], int g711u_bytes);

int g711a_encode(unsigned char g711_data[], const short amp[], int len);

int g711u_encode(unsigned char g711_data[], const short amp[], int len);

#endif  /* g711codec.h */

9.omx_decode.c

//   Copyright 2015-2016 Ansersion
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "bcm_host.h"
#include "ilclient.h"
#include "common.h"


COMPONENT_T *video_decode = NULL, *video_scheduler = NULL, *video_render = NULL, *video_clock = NULL;
COMPONENT_T *list[5];
TUNNEL_T tunnel[4];
ILCLIENT_T *client;
int status = 0;
unsigned int data_len = 0;
char *base_sps, *base_pps;
int debug = 1;

//设置播放速度
int SetSpeed(int speed){
		//speed=1000;正常速度
		OMX_TIME_CONFIG_SCALETYPE scaleType;
		OMX_INIT_STRUCTURE(scaleType);
		scaleType.xScale = (speed << 16) / 1000;//speed=0为暂停
		if(OMX_SetConfig(ILC_GET_HANDLE(video_clock),OMX_IndexConfigTimeScale, &scaleType)!= OMX_ErrorNone)
			printf("[clock]OMX_IndexConfigTimeScale error\n");
		return 0;
}
//设置视频透明度
int SetAlpha(int alpha){
		//if (debug) printf("set alpha:[%d]\n",alpha);
		OMX_CONFIG_DISPLAYREGIONTYPE configDisplay;
		OMX_INIT_STRUCTURE(configDisplay);
		configDisplay.nPortIndex =90;
		configDisplay.set = (OMX_DISPLAYSETTYPE)(OMX_DISPLAY_SET_ALPHA );
		configDisplay.alpha = alpha;
		if(OMX_SetConfig(ILC_GET_HANDLE(video_render),OMX_IndexConfigDisplayRegion, &configDisplay) != OMX_ErrorNone)
			printf("[video_render]OMX_IndexConfigDisplayRegion error\n");
		return 0;
}

//设置窗口位置
int SetRect(int x1,int y1,int x2,int y2){
	//if (debug) printf("set rect:[%d,%d,%d,%d]\n",x1,y1,x2,y2);
	OMX_CONFIG_DISPLAYREGIONTYPE configDisplay;
	OMX_INIT_STRUCTURE(configDisplay);
	configDisplay.nPortIndex =90;
	configDisplay.fullscreen = OMX_FALSE;
	configDisplay.noaspect   = OMX_TRUE;
	configDisplay.set                 = (OMX_DISPLAYSETTYPE)(OMX_DISPLAY_SET_DEST_RECT|OMX_DISPLAY_SET_SRC_RECT|OMX_DISPLAY_SET_FULLSCREEN|OMX_DISPLAY_SET_NOASPECT);
	configDisplay.dest_rect.x_offset  = x1;
	configDisplay.dest_rect.y_offset  = y1;
	configDisplay.dest_rect.width     = x2;
	configDisplay.dest_rect.height    = y2;
	/*
	//其他设置
	configDisplay.set = (OMX_DISPLAYSETTYPE)(OMX_DISPLAY_SET_ALPHA | OMX_DISPLAY_SET_TRANSFORM | OMX_DISPLAY_SET_LAYER | OMX_DISPLAY_SET_NUM);
	configDisplay.alpha = 200;
	configDisplay.num = 0;
	configDisplay.layer = 1;
	configDisplay.transform =0;//0正常 1镜像 2旋转180
	
	configDisplay.fullscreen = OMX_FALSE;
	configDisplay.noaspect   = OMX_TRUE;
    
	configDisplay.set                 = (OMX_DISPLAYSETTYPE)(OMX_DISPLAY_SET_DEST_RECT|OMX_DISPLAY_SET_SRC_RECT|OMX_DISPLAY_SET_FULLSCREEN|OMX_DISPLAY_SET_NOASPECT);
	configDisplay.dest_rect.x_offset  =200;
	configDisplay.dest_rect.y_offset  = 0;
	configDisplay.dest_rect.width     = 640;
	configDisplay.dest_rect.height    = 480;

	configDisplay.src_rect.x_offset   =1920;
	configDisplay.src_rect.y_offset   =0;
	configDisplay.src_rect.width      = 1920;
	configDisplay.src_rect.height     =1080;
	*/
	if(OMX_SetConfig(ILC_GET_HANDLE(video_render),OMX_IndexConfigDisplayRegion, &configDisplay) != OMX_ErrorNone)
		printf("[video_render]OMX_IndexConfigDisplayRegion error\n");
	return 0;
}
int omx_init(){
   bcm_host_init();
   memset(list, 0, sizeof(list));
   memset(tunnel, 0, sizeof(tunnel));

   if((client = ilclient_init()) == NULL){
	  status = -21;
	  printf("ilclient_init error\n");
      return status;
   }
   if(OMX_Init() != OMX_ErrorNone){
	  status = -21;
	  printf("OMX_Init error\n");
      ilclient_destroy(client);
      return status;
   }

   // create video_decode
   if(ilclient_create_component(client, &video_decode, "video_decode", ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS) != 0)
      status = -14;
   list[0] = video_decode;

   // create video_render
   if(status == 0 && ilclient_create_component(client, &video_render, "video_render", ILCLIENT_DISABLE_ALL_PORTS) != 0)
      status = -14;
   list[1] = video_render;

   // create clock
   if(status == 0 && ilclient_create_component(client, &video_clock, "clock", ILCLIENT_DISABLE_ALL_PORTS) != 0)
      status = -14;
   list[2] = video_clock;
   
   OMX_TIME_CONFIG_CLOCKSTATETYPE cstate;
   memset(&cstate, 0, sizeof(cstate));
   cstate.nSize = sizeof(cstate);
   cstate.nVersion.nVersion = OMX_VERSION;
   cstate.eState = OMX_TIME_ClockStateWaitingForStartTime;
   cstate.nWaitMask = 1;
   if(video_clock != NULL && OMX_SetParameter(ILC_GET_HANDLE(video_clock), OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone)
      status = -13;

   // create video_scheduler
   if(status == 0 && ilclient_create_component(client, &video_scheduler, "video_scheduler", ILCLIENT_DISABLE_ALL_PORTS) != 0)
      status = -14;
   list[3] = video_scheduler;

   set_tunnel(tunnel, video_decode, 131, video_scheduler, 10);
   set_tunnel(tunnel+1, video_scheduler, 11, video_render, 90);
   set_tunnel(tunnel+2, video_clock, 80, video_scheduler, 12);

   // setup clock tunnel first
   if(status == 0 && ilclient_setup_tunnel(tunnel+2, 0, 0) != 0)
      status = -15;
   else
      ilclient_change_component_state(video_clock, OMX_StateExecuting);

   if(status == 0)
      ilclient_change_component_state(video_decode, OMX_StateIdle);
  
   OMX_VIDEO_PARAM_PORTFORMATTYPE format;
   memset(&format, 0, sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE));
   format.nSize = sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE);
   format.nVersion.nVersion = OMX_VERSION;
   format.nPortIndex = 130;
   format.eCompressionFormat = OMX_VIDEO_CodingAVC;
   format.xFramerate = 30 * (1<<16);
   
   if(OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamVideoPortFormat, &format) != OMX_ErrorNone ){
	   status = -21;
   }
  //----------------------------------------------------------------
  
	OMX_PARAM_PORTDEFINITIONTYPE portParam;
	OMX_INIT_STRUCTURE(portParam);
	portParam.nPortIndex = 130;
	if(OMX_GetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamPortDefinition, &portParam)!= OMX_ErrorNone)
		printf("OMX_GetParameter OMX_IndexParamPortDefinition error!\n");

	portParam.nBufferSize=100*1024;//默认81920,不要轻易改动
	//portParam.nBufferCountMin=2;
	//portParam.nBufferCountActual=100;
	//portParam.format.video.nFrameWidth  = 1920;
	//portParam.format.video.nFrameHeight = 1080;
	if(OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamPortDefinition, &portParam)!= OMX_ErrorNone)
		printf("OMX_SetParameter OMX_IndexParamPortDefinition error\n!");
	if(OMX_GetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamPortDefinition, &portParam)!= OMX_ErrorNone)
		printf("OMX_GetParameter OMX_IndexParamPortDefinition error\n!");
	printf("portParam.nBufferSize=%d\n",portParam.nBufferSize);
   
  //----------------------------------------------------------------
 
    //有效帧开始
    
	OMX_PARAM_BRCMVIDEODECODEERRORCONCEALMENTTYPE concanParam;
	OMX_INIT_STRUCTURE(concanParam);
	concanParam.bStartWithValidFrame = OMX_FALSE;//OMX_FALSE OMX_TRUE
	if(OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamBrcmVideoDecodeErrorConcealment, &concanParam)!= OMX_ErrorNone)
		printf("OMX_SetParameter OMX_IndexParamBrcmVideoDecodeErrorConcealment error!\n");

	//request portsettingschanged on aspect ratio change
	OMX_CONFIG_REQUESTCALLBACKTYPE notifications;
	OMX_INIT_STRUCTURE(notifications);
	notifications.nPortIndex = 131;//OutputPort
	notifications.nIndex = OMX_IndexParamBrcmPixelAspectRatio;
	notifications.bEnable = OMX_TRUE;
	if (OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexConfigRequestCallback, ¬ifications) != OMX_ErrorNone)
		printf("[video_decode]OMX_SetParameter OMX_IndexConfigRequestCallback error!\n");
	
   //----------------------------------------------------------
	
	OMX_CONFIG_BOOLEANTYPE timeStampMode;
	OMX_INIT_STRUCTURE(timeStampMode);
	timeStampMode.bEnabled = OMX_TRUE;
	if (OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamBrcmVideoTimestampFifo, &timeStampMode) != OMX_ErrorNone)
		printf("[video_decode]OMX_SetParameter OMX_IndexParamBrcmVideoTimestampFifo error\n!");
   
   //----------------------------------------------------------
   
   if(ilclient_enable_port_buffers(video_decode, 130, NULL, NULL, NULL) != 0){
	   status = -22;
   }
   if(status==0){
	   ilclient_change_component_state(video_decode, OMX_StateExecuting);
   }
   printf("omx init succefull\n");
   return status;
}



float pts,recpts;
unsigned long start_timestamp;
int first_packet = 1;
int omx_decode(unsigned char *videobuffer,int videobuffer_len,unsigned long timestamp){
//printf("[omx_decode]videobuffer_len=%d\n",videobuffer_len);
usleep(0);//防止cpu占用100%,不知道原因
   if(status == 0){
	    
	    //pts获取
		OMX_TIME_CONFIG_TIMESTAMPTYPE timeStamp;
		OMX_INIT_STRUCTURE(timeStamp);
		timeStamp.nPortIndex =80;//OMX_IndexConfigTimeCurrentMediaTime
		if(OMX_GetConfig(ILC_GET_HANDLE(video_clock),OMX_IndexConfigTimeCurrentMediaTime, &timeStamp)== OMX_ErrorNone){
			pts = (double)FromOMXTime(timeStamp.nTimestamp);
			//if (debug)printf("pts:%.2f [%.0f]--recpts:%.2f [%.0f]\n",  (double)pts* 1e-6, (double)pts,(double)recpts* 1e-6, (double)recpts);
		}
	   
	   
      OMX_BUFFERHEADERTYPE *buf;
      int port_settings_changed = 0;//每次为0可以在切换码流时候再次port_settings_changed
      

	if((buf = ilclient_get_input_buffer(video_decode, 130, 1)) != NULL){
		if(buf->nAllocLen<videobuffer_len)	printf("buf-nAllocLen=%d,videobuffer_len=%d\n",buf->nAllocLen,videobuffer_len);
		memcpy(buf->pBuffer,videobuffer,videobuffer_len);
		//free(videobuffer);//对应的是video_rtp_recv.c中的in_buffer=(unsigned char *)malloc(outbuffer_len);
		buf->nFilledLen = videobuffer_len;
        buf->nOffset = 0;
		recpts=((double)timestamp-(double)start_timestamp)/90000;//注意计算方式不一样
		float video_fifo=recpts-(double)pts* 1e-6;
		//printf("[pts]:encode[%.2f]-decode[%.2f]=[%.2f]\n",  recpts,(double)pts* 1e-6, video_fifo);
		//调节速度
		/*
		if(video_fifo<0.19){
			SetSpeed(995);
		}else if(video_fifo>0.21){
			SetSpeed(1010);
		}
		*/
		//-------------------------------------------------------------------------------
		if(port_settings_changed == 0 &&(videobuffer_len > 0 && ilclient_remove_event(video_decode, OMX_EventPortSettingsChanged, 131, 0, 0, 1) == 0)){
			printf("-------------***-port_settings_changed--***---------------\n");
			first_packet = 1;//***在视频切换或改变的时候,解码器的时间戳继续按原来的走,还不知道怎么把它初始化为0(pts的值)
			port_settings_changed = 1;
			if(ilclient_setup_tunnel(tunnel, 0, 0) != 0)         status = -7;
			ilclient_change_component_state(video_scheduler, OMX_StateExecuting);
			if(ilclient_setup_tunnel(tunnel+1, 0, 1000) != 0)       status = -12;
			ilclient_change_component_state(video_render, OMX_StateExecuting);
		}
			
         
         if(first_packet){
			/*
			//设置开始时间戳,从rtp包中获取到时间戳,但是不正确,采取时间戳相减
			OMX_TIME_CONFIG_TIMESTAMPTYPE sClientTimeStamp;
			OMX_INIT_STRUCTURE(sClientTimeStamp);
			if(OMX_GetConfig(ILC_GET_HANDLE(video_clock), OMX_IndexConfigTimeCurrentWallTime, &sClientTimeStamp)!=OMX_ErrorNone)
			printf("OMX_GetConfig OMX_IndexConfigTimeClientStartTime error\n!");
			sClientTimeStamp.nPortIndex=80;
			sClientTimeStamp.nTimestamp=ToOMXTime(timestamp);//设置开始时间戳
			if( OMX_SetConfig(ILC_GET_HANDLE(video_clock), OMX_IndexConfigTimeClientStartTime, &sClientTimeStamp)!=OMX_ErrorNone)
			printf("OMX_SetConfig OMX_IndexConfigTimeClientStartTime error\n!");
			*/
			start_timestamp=timestamp;
			//if (debug)printf("start_timestamp=%d\n",timestamp);
            buf->nFlags = OMX_BUFFERFLAG_STARTTIME;
            //buf->nTimeStamp=pts;
            first_packet = 0;
         }
         else{
            buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN;
		    //buf->nTimeStamp.nLowPart=0;
		    //buf->nTimeStamp.nHighPart=0;
		 }
         if(OMX_EmptyThisBuffer(ILC_GET_HANDLE(video_decode), buf) != OMX_ErrorNone)      status = -6;
      
      //ilclient_wait_for_event(video_render, OMX_EventBufferFlag, 90, 0, OMX_BUFFERFLAG_EOS, 0,ILCLIENT_BUFFER_FLAG_EOS, 10000);
      //ilclient_flush_tunnels(tunnel, 0);
	}
    }

	return status;
}

int omx_deinit(){
	pts=0;
	recpts=0;
	start_timestamp=0;
	first_packet = 1;//一定要注意,否则第二次呼入不解码,不显示图像
	ilclient_disable_tunnel(tunnel);
	ilclient_disable_tunnel(tunnel+1);
	ilclient_disable_tunnel(tunnel+2);
	ilclient_disable_port_buffers(video_decode, 130, NULL, NULL, NULL);
	ilclient_teardown_tunnels(tunnel);
	ilclient_state_transition(list, OMX_StateIdle);
	ilclient_state_transition(list, OMX_StateLoaded);
	ilclient_cleanup_components(list);
	OMX_Deinit();
	ilclient_destroy(client);
	return 0;
}

10.omx_decode.h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bcm_host.h"
#include "ilclient.h"
#include "common.h"
int omx_init();
int omx_decode(unsigned char *videobuffer,int videobuffer_len,unsigned long timestamp);
int omx_deinit();
int SetAlpha(int alpha);
int SetSpeed(int speed);
int SetRect(int x1,int y1,int x2,int y2);

11.queue.c

#include <stdio.h>  
#include <stdlib.h> 
#include "queue.h"
//------------------cache-start--------------------

    /************************************************* 
    Function:       InitQueue 
    Description:    初始化,构造空队列 
    Input:          队列指针 CircleQueue *queue 
    Output: 
    Return:         成功返回OK 
    Others:         空队列 queue->front = queue->rear = 0 
    *************************************************/  
    int InitQueue(CircleQueue *queue)  
    {  
        queue->front = queue->rear = 0;  
        queue->count = 0;  
        return OK;  
      
    }  
      
    //判断队列为空和满  
    //1、使用计数器count,队列为空和满时,front都等于rear  
    //2、少用一个元素的空间,约定队列满时:(rear+1)%QUEUESIZE=front,为空时front=rear  
    //rear指向队尾元素的下一个位置,始终为空;队列的长度为(rear-front+QUEUESIZE)%QUEUESIZE  
      
    /************************************************* 
    Function:       IsQueueEmpty 
    Description:    队列是否为空 
    Input:          队列指针 CircleQueue *queue 
    Output: 
    Return:         为空返回TRUE,否则返回FALSE 
    Others: 
    *************************************************/  
    int IsQueueEmpty(CircleQueue *queue)  
    {  
        if(queue->count == 0)  
            return TRUE;  
        else  
            return FALSE;  
      
      
    }  
      
    /************************************************* 
    Function:       IsQueueFull 
    Description:    队列是否为满 
    Input:          队列指针 CircleQueue *queue 
    Output: 
    Return:         为满返回TRUE,否则返回FALSE 
    Others: 
    *************************************************/  
    int IsQueueFull(CircleQueue *queue)  
    {  
        if(queue->count == QUEUESIZE)  
            return TRUE;  
        else  
            return FALSE;  
      
      
    }  
      
    /************************************************* 
    Function:       EnQueue 
    Description:    入队 
    Input:          队列指针 CircleQueue *queue 
                    数据元素   ElemType e 
    Output: 
    Return:         成功返回OK,失败返回ERROR 
    Others: 
    *************************************************/  
    int EnQueue(CircleQueue *queue, ElemType e)  
    {  
        //验证队列是否已满  
        if(queue->count == QUEUESIZE)  
        {  
            printf("The queue is full");  
            return ERROR;  
        }  
        //入队  
        queue->data[queue->rear] = e;  
        //对尾指针后移  
        queue->rear = (queue->rear + 1) % QUEUESIZE;  
        //更新队列长度  
        queue->count++;  
//printf("e.data=%p\n",*(e.data));
        return OK;  
      
    }  
      
    /************************************************* 
    Function:       DeQueue 
    Description:    出队 
    Input:          队列指针 CircleQueue *queue 
    Output: 
    Return:         成功返回数据元素,失败程序退出 
    Others: 
    *************************************************/  
    ElemType DeQueue(CircleQueue *queue)  
    {  
        //判断队列是否为空  
        if(queue->count == 0)  
        {  
            printf("The queue is empty!");  
            exit(EXIT_FAILURE);  
        }  
      
        //保存返回值  
        ElemType e = queue->data[queue->front];  
//free(queue->data[queue->front].data);
        //更新队头指针  
        queue->front = (queue->front + 1) % QUEUESIZE;  
        //更新队列长度  
        queue->count--;  
      
        return e;  
      
    }  
      
    /************************************************* 
    Function:       GetHead 
    Description:    取队头元素 
    Input:          队列指针 CircleQueue *queue 
    Output: 
    Return:         成功返回数据元素,否则程序退出 
    Others: 
    *************************************************/  
    ElemType GetHead(CircleQueue *queue)  
    {  
        //判断队列是否为空  
        if(queue->count == 0)  
        {  
            printf("The queue is empty!");  
            exit(EXIT_FAILURE);  
        }  
      
        return queue->data[queue->front];  
      
    }  
      
    /************************************************* 
    Function:       ClearQueue 
    Description:    清空队列 
    Input:          队列指针 CircleQueue *queue 
    Output: 
    Return:         成功返回OK 
    Others: 
    *************************************************/  
    int ClearQueue(CircleQueue *queue )  
    {  
        queue->front = queue->rear = 0;  
        queue->count = 0;  
        return OK;  
      
    }  
      
    /************************************************* 
    Function:       GetLength 
    Description:    取得队列的长度 
    Input:          队列指针 CircleQueue *queue 
    Output: 
    Return:         返回队列的长度 
    Others: 
    *************************************************/  
    int GetLength(CircleQueue *queue)  
    {  
        return queue->count;  
           
    }  


12.queue.h

#include <stdio.h>  
#include <stdlib.h> 
#include <stdint.h>
//------------------cache-start--------------------
    //定义函数结果状态码  
    #define OK 1  
    #define ERROR 0  
    #define TRUE 1  
    #define FALSE 0  
      
    //定义循环队列空间大小  
    #define QUEUESIZE 100
      typedef struct _frame 
    {  
       //unsigned char *data;//存储队列元素  
        uint8_t *data;
        //int len;//队列头指针  
        size_t len;
        unsigned  long  pts;
        unsigned  long  start_pts;
        //unsigned  long  dts;
        uint32_t timestamp;
        uint16_t seqnum;
        int64_t start_timestamp;
    }frame;  //frame
      

      
      
    //定义数据类型  
    typedef frame ElemType ;  
      
    //循环队列存储结构  
    typedef struct _CircleQueue  
    {  
        ElemType data[QUEUESIZE];//存储队列元素  
        int front;//队列头指针  
        int rear;//队列尾指针  
        int count;//队列元素个数  
    }CircleQueue;  
      
      
    int InitQueue(CircleQueue *queue)  ;     
    int IsQueueEmpty(CircleQueue *queue)  ;
    int IsQueueFull(CircleQueue *queue)  ;
    int EnQueue(CircleQueue *queue, ElemType e)  ;
    ElemType DeQueue(CircleQueue *queue)  ;
    ElemType GetHead(CircleQueue *queue)  ;
    int ClearQueue(CircleQueue *queue )  ;
    int GetLength(CircleQueue *queue)  ;

//---------------cache-end------------

13.rtpavcsend.c  (小日本写的,改了下)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "rtpavcsend.h"

#pragma pack(1)	

typedef struct rtphead_t {
	unsigned char head1;		
	unsigned char ptype;		
	unsigned short seqno;	// sequence number
	unsigned long timestamp;	
	unsigned long ssrc;		
} RtpHead;
#pragma pack()


int rtpopen(RtpSend** pctx_out, unsigned long ssrc, int ptype, int sock, struct sockaddr_in *peer)
{
	RtpSend* ctx = (RtpSend*)malloc(sizeof(RtpSend));
	memset(ctx, 0, sizeof(RtpSend));

	memcpy(&ctx->peer, peer, sizeof(struct sockaddr_in));
	ctx->seqno = 1;		
	ctx->ssrc  = ssrc;
	ctx->ptype = ptype;
	ctx->sock  = sock;
	*pctx_out = ctx;
	return 0;
}

static int rtpsend_nal(RtpSend* ctx, const unsigned char* data, int datalen, unsigned long timestamp)
{
	unsigned char nal_unit_type;
	unsigned char* payload;
	unsigned char buff[4096];
	int single_NAL = 1;
	RtpHead *head = (RtpHead*)buff;
	head->head1 = 0x80;
	head->ptype = ctx->ptype;
	head->seqno = htons(ctx->seqno);
	head->timestamp = htonl(timestamp);
	head->ssrc = htonl(ctx->ssrc);

	payload = (unsigned char*)(head+1);	
	nal_unit_type = data[0] & 0x1f;

	// NAL unit type
	switch (nal_unit_type) {
	case 7://SPS
	case 8://PPS
	case 6://SEI
	case 9://AUD
		//printf("nal_unit_type=%d\n",nal_unit_type);
		head->ptype |= 0x80;//by aphero!小日本编写的有点错误,找了半天,才发现sps pps 中的maker标志为不正确,暂时在这里修改
		break;
	default:
		if (datalen > 1300)
			single_NAL = 0;	// 1300
		break;
	}
	if (single_NAL) {
		
		memcpy(payload, data, datalen);
	
		if (sendto(ctx->sock, buff, datalen+sizeof(RtpHead), 0, (struct sockaddr*)&ctx->peer, sizeof(ctx->peer)) < 0)
			return -1;
		ctx->seqno++;
		return 0;	
	}

	payload[0] = ((data[0]&0x60) | (28/*FU-A*/&0x1f));	// FUindicator: nal_ref_idc NALtype=28
	payload[1] = (data[0]&0x1f);							// FUheader:  
	data++; datalen--; 	

	payload[1] |= 0x80;	//
	while (datalen > 0) {
		int len = datalen < 1300 ? datalen : 1300;	
		memcpy(payload+2, data, len);	
		if (len == datalen) {
			payload[1] |= 0x40;	
			head->ptype |= 0x80;	
		}
		
		if (sendto(ctx->sock, buff, sizeof(RtpHead)+2+len, 0, (struct sockaddr*)&ctx->peer, sizeof(ctx->peer)) < 0)
			return -1;
		ctx->seqno++;	
		head->seqno = htons(ctx->seqno);
		payload[1] &= 0x7f;	
		data += len; datalen -= len;
	}

	return 0;
}

void rtpclose(RtpSend* ctx)
{
	close(ctx->sock);
	free(ctx);
}

// RTP
unsigned long long last_ts64 =0;
static unsigned long make_timestamp()
{
	struct timeval tv;
	gettimeofday(&tv, NULL);
	unsigned long long ts64 = (unsigned long long)tv.tv_sec * 90000 + tv.tv_usec*90/1000;
	//printf("timestamp=%lld\n",ts64-last_ts64);
	last_ts64=ts64;
	return (unsigned long)(ts64 & 0xffffffff);
}


int AvcAnalyzeAndSend(RtpSend* ctx, const unsigned char* data, int datalen)
{
	const unsigned char _startcode[] = {0,0,1};
	const unsigned long ts = make_timestamp();
	int nal;	
	int i;		
	int begin;	

	if (ctx->pending_len == 0) {
		
		for (i=0; i<datalen-sizeof(_startcode); i++) {
			if (memcmp(data+i, _startcode, sizeof(_startcode)) == 0) {
				
				i += sizeof(_startcode);
				memcpy(ctx->pending_buff, data+i, datalen-i);
				data = ctx->pending_buff;
				datalen = ctx->pending_len = datalen-i;
				begin = 0;
				break;
			}
		}
		if (ctx->pending_len == 0)	
			return 0;
	}
	else {
		begin = ctx->pending_len - sizeof(_startcode)+1;	
		if (begin < 0) begin = 0;
		
		memcpy(ctx->pending_buff + ctx->pending_len, data, datalen);
		data = ctx->pending_buff;
		datalen = ctx->pending_len = ctx->pending_len + datalen;
	}

	nal = 0;
	for (i=begin; i<datalen-sizeof(_startcode); i++) {
		// startcode
		if (memcmp(&data[i], _startcode, sizeof(_startcode)) != 0)
			continue;
		else {
			// startcode
			int pre0 = 0;
			while (nal < i-pre0 && data[i-pre0-1] == 0) pre0++;
			const int nallen = i-nal - pre0;
			if (nallen > 0)
				rtpsend_nal(ctx, &data[nal], nallen, ts);
		}
		nal = i + sizeof(_startcode);
		i = nal-1;
	}
	if (nal > 0 && datalen - nal > 0) { 
		memmove(&ctx->pending_buff[0], &data[nal], datalen-nal);
		ctx->pending_len = datalen - nal;
	}

	return 0;
}

14.rtpavcsend.h

#ifndef _RTPAVCSEND_H
#define _RTPAVCSEND_H

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


typedef struct RtpSend_t {
	int sock;					
	struct sockaddr_in peer;	
	unsigned short seqno;	
	unsigned char ptype;		
	unsigned long ssrc;		
	unsigned char pending_buff[1024*640];	
	int pending_len;	
} RtpSend;

//=============================
int rtpopen(RtpSend** pctx_out, unsigned long ssrc, int ptype, int sock, struct sockaddr_in *peer);
void rtpclose(RtpSend* ctx);

//=============================
int AvcAnalyzeAndSend(RtpSend* ctx, const unsigned char* data, int datalen);
#endif	/* _RTPAVCSEND_H */

15.sip.c   程序入口


#include <stdio.h>  
#include <stdlib.h>  
#include <stdint.h>
#include <netinet/in.h>  
#include <sys/socket.h>  
#include <arpa/inet.h>
#include <sys/types.h>  
#include <pthread.h>
#include <osip2/osip_mt.h> 
#include <eXosip2/eXosip.h>  
#include "audio_rtp_send.h"
#include "audio_rtp_recv.h"
#include "video_rtp_send.h"
#include "video_rtp_recv.h"




eXosip_event_t *je;  
osip_message_t *reg = NULL;  
osip_message_t *invite = NULL;  
osip_message_t *ack = NULL;  
osip_message_t *info = NULL;  
osip_message_t *message = NULL;  
osip_message_t *answer = NULL;
sdp_message_t *remote_sdp = NULL;
sdp_connection_t * con_req = NULL;
sdp_media_t * md_audio_req = NULL; 
sdp_media_t * md_video_req = NULL; 

struct osip_thread *event_thread;
struct osip_thread *audio_thread_send;
struct osip_thread *audio_thread_recv;
struct osip_thread *video_thread_send;
struct osip_thread *video_thread_recv;

int call_id, dialog_id,calling ;  
int i,flag;  
int quit_flag = 1;  
int id;  
char command;  
char tmp[4096];  
char localip[128];  

//下面是需要预定义的参数
char *identity = "sip:1009@192.168.1.114";  
char *registerer = "sip:192.168.1.114";  
char *source_call = "sip:1009@192.168.1.114";  
char *proxy="sip:192.168.1.114"; 
char *dest_call ="sip:192.168.1.133:5062";// "sip:1001@192.168.1.114";
char *fromuser="sip:1009@192.168.1.114";  //"sip:1008@192.168.1.114";  
char *userid="1009";
char *passwd="12345";
struct audio_param_send audiosendparam={0/*audio_rtp_socket*/,"plughw:1,0"/*"hw:1,0"*/,NULL,0,54000,1,1};//音频线程初始化默认值,树莓派下usb声卡hw:1,0
struct audio_param_recv audiorecvparam={0/*audio_rtp_socket*/,"hw:1,0",NULL,0,54000,1};//音频线程初始化默认值,树莓派下usb声卡hw:1,0
struct video_param_send videosendparam={0/*video_rtp_socket*/,"/dev/video0",NULL,0,54002,1280,720,30,4000,1,1,1,1};//视频线程初始化默认值
struct video_param_recv videorecvparam={0/*video_rtp_socket*/,"/dev/video0",NULL,0,54002,640,480,15,8000,1,1,1,1};//视频线程初始化默认值

int open_audio_socket(){
	//音频socket
	int    audio_rtp_socket;
	struct sockaddr_in server;
	int len = sizeof(server);
	server.sin_family = AF_INET;
	server.sin_port = htons(audiosendparam.dest_port);
	server.sin_addr.s_addr = inet_addr(audiosendparam.dest_ip);
	audio_rtp_socket = socket(AF_INET,SOCK_DGRAM|SOCK_NONBLOCK,0);
	//设置超时
	struct timeval timeout={1,0};//1s
	//int ret=setsockopt(sock_fd,SOL_SOCKET,SO_SNDTIMEO,(const char*)&timeout,sizeof(timeout));
	if(setsockopt(audio_rtp_socket,SOL_SOCKET,SO_RCVTIMEO,(const char*)&timeout,sizeof(timeout))<0){
	printf("setsockopt timeout fail");
	return -1;
	}
	//端口复用
	int flag=1;
	if( setsockopt(audio_rtp_socket, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int)) == -1)  {  
	fprintf(stderr, RED "[%s@%s,%d]:"NONE"socket setsockopt error\n", __func__, __FILE__, __LINE__);
	return -1;
	}  
	//绑定本地端口
	struct sockaddr_in local;  
	local.sin_family=AF_INET;  
	local.sin_port=htons(audiosendparam.local_port);            ///监听端口  
	local.sin_addr.s_addr=INADDR_ANY;       ///本机  
	if(bind(audio_rtp_socket,(struct sockaddr*)&local,sizeof(local))==-1) {
	fprintf(stderr, RED "[%s@%s,%d]:"NONE"udp port bind error\n",__func__, __FILE__, __LINE__);
	return -1;
	}
	connect(audio_rtp_socket, (struct sockaddr *)&server, len);
	audiosendparam.audio_rtp_socket=audiorecvparam.audio_rtp_socket=audio_rtp_socket;
	fprintf(stderr,GREEN"[%s]:"NONE"open_audio_socket!\n",__FILE__);
	return 0;
}

int open_video_socket(){
	//视频socket
	int    video_rtp_socket;
	struct sockaddr_in server;
	int len = sizeof(server);
	server.sin_family = AF_INET;
	server.sin_port = htons(videosendparam.dest_port);
	server.sin_addr.s_addr = inet_addr(videosendparam.dest_ip);
	video_rtp_socket = socket(AF_INET,SOCK_DGRAM|SOCK_NONBLOCK,0);
	//设置超时
	struct timeval timeout={1,0};//1s
	//int ret=setsockopt(sock_fd,SOL_SOCKET,SO_SNDTIMEO,(const char*)&timeout,sizeof(timeout));
	if(setsockopt(video_rtp_socket,SOL_SOCKET,SO_RCVTIMEO,(const char*)&timeout,sizeof(timeout))<0){
	printf("setsockopt timeout fail");
	return -1;
	}
	
	//端口复用
	int flag=1;
	if( setsockopt(video_rtp_socket, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int)) == -1)  {  
	fprintf(stderr, RED "[%s@%s,%d]:"NONE"socket setsockopt error\n", __func__, __FILE__, __LINE__);
	return -1;
	}  
	
	//设置接收缓存
	int recv_size=20*1024*1024;
	if(setsockopt(video_rtp_socket,SOL_SOCKET,SO_RCVBUF,(char*)&recv_size,sizeof(recv_size))<0){
	printf("setsockopt recv_buff fail");
	return -1;
	}
	//设置发送缓存
	int send_size=20*1024*1024;
	if(setsockopt(video_rtp_socket,SOL_SOCKET,SO_SNDBUF,(char*)&send_size,sizeof(send_size))<0){
	printf("setsockopt send_buff fail");
	return -1;
	}

	//绑定本地端口
	struct sockaddr_in local;  
	local.sin_family=AF_INET;  
	local.sin_port=htons(videosendparam.local_port);            ///监听端口  
	local.sin_addr.s_addr=INADDR_ANY;       ///本机  
	if(bind(video_rtp_socket,(struct sockaddr*)&local,sizeof(local))==-1) {
	fprintf(stderr, RED "[%s@%s,%d]:"NONE"udp port bind error\n",__func__, __FILE__, __LINE__);
	return -1;
	}
	connect(video_rtp_socket, (struct sockaddr *)&server, len);
	videosendparam.video_rtp_socket=videorecvparam.video_rtp_socket=video_rtp_socket;
	fprintf(stderr,GREEN"[%s]:"NONE"open_video_socket!\n",__FILE__);
	return 0;
}

 void  *sipEventThread() 
{  
  eXosip_event_t *je;  

  for (;;)  
  {  
      je = eXosip_event_wait (0, 100);  
      eXosip_lock();  
      eXosip_automatic_action ();  //401,407错误
      eXosip_unlock();  
  
      if (je == NULL)  
        continue;  
  
      switch (je->type)  
     {  
		 /* REGISTER related events 1-4*/
		case EXOSIP_REGISTRATION_NEW:  
			printf("received new registration\n");
			break;  

		case EXOSIP_REGISTRATION_SUCCESS:   
			printf( "registrered successfully\n");
			break;  

		case EXOSIP_REGISTRATION_FAILURE:
			printf("EXOSIP_REGISTRATION_FAILURE!\n");
			break;
			
		case EXOSIP_REGISTRATION_REFRESHED:
			printf("REGISTRATION_REFRESHED\n");
			break;

		case EXOSIP_REGISTRATION_TERMINATED:  
			printf("Registration terminated\n");
			break;  
		/* INVITE related events within calls */
		case EXOSIP_CALL_INVITE:  
			 printf ("Received a INVITE msg from %s:%s, UserName is %s, password is %s\n",je->request->req_uri->host,
			je->request->req_uri->port, je->request->req_uri->username, je->request->req_uri->password);
			calling = 1;
			eXosip_lock();
			eXosip_call_send_answer (je->tid, 180, NULL);
			i = eXosip_call_build_answer (je->tid, 200, &answer);
			if (i != 0)
			{
			printf ("This request msg is invalid!Cann't response!/n");
			eXosip_call_send_answer (je->tid, 400, NULL);
			}
			else
			{
			char localip[128]; 
			eXosip_guess_localip(AF_INET, localip, 128); 
			snprintf (tmp, 4096,
					"v=0\r\n"
					"o=- 0 0 IN IP4 %s\r\n"
					"s=No Name\r\n"
					"c=IN IP4 %s\r\n"
					"t=0 0\r\n"
					"m=audio %d RTP/AVP 0\r\n"
					"a=rtpmap:0 PCMU/8000\r\n"
					"m=video 54002 RTP/AVP 96\r\n"
					//"b=AS:4096\r\n"
					"a=rtpmap:96 H264/90000\r\n"
					//"a=fmtp:96 packetization-mode=1;\r\n"
					,localip, localip,audiosendparam.local_port
			   );

			//设置回复的SDP消息体,下一步计划分析消息体
			//没有分析消息体,直接回复原来的消息,这一块做的不好。
			osip_message_set_body (answer, tmp, strlen(tmp));
			osip_message_set_content_type (answer, "application/sdp");
			eXosip_call_send_answer (je->tid, 200, answer);
			printf ("send 200 over!\n");
			}
			eXosip_unlock ();
     
			printf ("the INFO is :\n");
			//得到消息体,该消息就是SDP格式.
			remote_sdp = eXosip_get_remote_sdp (je->did);
			con_req = eXosip_get_audio_connection(remote_sdp);
			md_audio_req = eXosip_get_audio_media(remote_sdp); 
			md_video_req = eXosip_get_video_media(remote_sdp); 
			char *remote_sdp_str=NULL;
			sdp_message_to_str(remote_sdp,&remote_sdp_str);
			printf("remote_sdp_str=======================\n%s\n",remote_sdp_str);
			
			char *payload_str; 
			int pos = 0;
			printf("audio info:----------------\n");
			while (!osip_list_eol ( (const osip_list_t *)&md_audio_req->m_payloads, pos))
			{
				payload_str = (char *)osip_list_get(&md_audio_req->m_payloads, pos);//获取媒体的pt(0,8)
				sdp_attribute_t *at;
				at = (sdp_attribute_t *) osip_list_get ((const osip_list_t *)&md_audio_req->a_attributes, pos);
				printf("payload_str=%s,m_media=%s\n",payload_str,at->a_att_value);
				pos++;
			}
			
			printf("video info:----------------\n");
			 pos = 0;
			while (!osip_list_eol ( (const osip_list_t *)&md_video_req->m_payloads, pos))
			{
				payload_str = (char *)osip_list_get(&md_video_req->m_payloads, pos);//获取媒体的pt(96)
				sdp_attribute_t *at;
				at = (sdp_attribute_t *) osip_list_get ((const osip_list_t *)&md_video_req->a_attributes, pos);
				printf("payload_str=%s,m_media=%s\n",payload_str,at->a_att_value);
				pos++;
			}
			printf("--------------------------------------------------\n");
			printf("SDP:conn_add=%s,audio_port=%s,video_port=%s\n",con_req->c_addr,md_audio_req->m_port,md_video_req->m_port);
			printf("--------------------------------------------------\n");
			
			char ip[20];
			strcpy(ip,con_req->c_addr);//这个地方不知道什么原因 
			//传入音频线程参数
			audiosendparam.dest_ip=ip; 
			audiorecvparam.dest_ip=ip; 
			audiosendparam.dest_port=audiorecvparam.dest_port=atoi(md_audio_req->m_port);
			//audioparam.audio_hw = "default";
			//传入视频线程参数
			videosendparam.dest_ip=ip;
			videorecvparam.dest_ip=ip;
			videosendparam.dest_port=videorecvparam.dest_port=atoi(md_video_req->m_port);
		
			sdp_message_free(remote_sdp);
			remote_sdp = NULL;
			break;  
		case EXOSIP_CALL_REINVITE:
			printf("REINVITE\n");
			break;
		case EXOSIP_CALL_NOANSWER:
			break;
		case EXOSIP_CALL_PROCEEDING:  
			printf ("proceeding!\n");  
			break;  
		case EXOSIP_CALL_RINGING:  
			printf ("ringing!\n");  
			printf ("call_id is %d, dialog_id is %d \n", je->cid, je->did);  
			break;  
		case EXOSIP_CALL_ANSWERED:  
			printf ("ok! connected!\n");  
			call_id = je->cid;  
			dialog_id = je->did;  
			printf ("call_id is %d, dialog_id is %d \n", je->cid, je->did);  
			eXosip_call_build_ack (je->did, &ack);  
			eXosip_call_send_ack (je->did, ack);  
			
			break;  
		case EXOSIP_CALL_REDIRECTED:
			break;
		case EXOSIP_CALL_REQUESTFAILURE:
			break;
		case EXOSIP_CALL_SERVERFAILURE:
			break;
		case EXOSIP_CALL_GLOBALFAILURE:
			break;
		case EXOSIP_CALL_CANCELLED:
			break;
		case EXOSIP_CALL_TIMEOUT:
			break;
		case EXOSIP_CALL_CLOSED:  
			printf ("the call sid closed!\n");  //呼叫结束
			videorecvparam.recv_quit=0;
			videosendparam.send_quit=0;
			audiorecvparam.recv_quit=0;
			audiosendparam.send_quit=0;
			int thread_rc=-1;
			thread_rc=osip_thread_join(audio_thread_send);
			if (thread_rc==0) fprintf(stderr,GREEN"[%s]:"NONE"audio_send_thread exit\n",__FILE__);
			thread_rc=osip_thread_join(audio_thread_recv);
			if (thread_rc==0) fprintf(stderr,GREEN"[%s]:"NONE"audio_recv_thread exit\n",__FILE__);
			thread_rc=osip_thread_join(video_thread_send);
			if (thread_rc==0) fprintf(stderr,GREEN"[%s]:"NONE"video_send_thread exit\n",__FILE__);
			thread_rc=osip_thread_join(video_thread_recv);
			if (thread_rc==0) fprintf(stderr,GREEN"[%s]:"NONE"video_thread_recv exit\n",__FILE__);
			break;  
		case EXOSIP_CALL_ACK:  
			printf ("ACK received!\n");  
			call_id = je->cid;  
			dialog_id = je->did;  
			//获取到远程的sdp信息后,分别建立音频 视频4个线程
			videorecvparam.recv_quit=1;
			videosendparam.send_quit=1;
			audiorecvparam.recv_quit=1;
			audiosendparam.send_quit=1;
			if(open_audio_socket()<0) break;//打开音频发送接收socket
			//音频发送线程
			
			eXosip_lock();  
			printf("conn_add=%s,audio_port=%d\n",audiosendparam.dest_ip,audiosendparam.dest_port);
			audio_thread_send = osip_thread_create (20000, &audio_send , &audiosendparam);//开启音频线程
			if (audio_thread_send==NULL){
				fprintf(stderr,RED"[%s]:"NONE"audio_send_thread_create failed\n",__FILE__);
			}
			else{
				fprintf(stderr,GREEN"[%s]:"NONE"audio_send_thread created!\n",__FILE__);
			}
			 eXosip_unlock();
			 
			//音频接收线程
			
			eXosip_lock();  
			audio_thread_recv = osip_thread_create (20000, &audio_recv ,&audiorecvparam);//开启音频线程
			if (audio_thread_recv==NULL){
				fprintf(stderr,RED"[%s]:"NONE"audio_thread_recv failed\n",__FILE__);
			}
			else{
				fprintf(stderr,GREEN"[%s]:"NONE"audio_thread_recv created!\n",__FILE__);
			}
			 eXosip_unlock(); 
			
			
			if(open_video_socket()<0) break;//打开视频发送接收socket
			//视频发送线程
			//=======================================
			
			eXosip_lock();  
			video_thread_send = osip_thread_create (20000, video_send , &videosendparam);
			if (video_thread_send==NULL){
				fprintf(stderr,RED"[%s]:"NONE"video_thread_send failed\n",__FILE__);
			}
			else{
				fprintf(stderr,GREEN"[%s]:"NONE"video_thread_send created!\n",__FILE__);
			}
			 eXosip_unlock(); 
			 
			//视频接收线程
			
			eXosip_lock();  
			video_thread_recv = osip_thread_create (20000, video_recv,&videorecvparam);
			if (video_thread_recv==NULL){
				fprintf(stderr,RED"[%s]:"NONE"video_thread_recv failed\n",__FILE__);
			}
			else{
				fprintf(stderr,GREEN"[%s]:"NONE"video_thread_recv created!\n",__FILE__);
			}
			eXosip_unlock(); 
			
			break;  
		case EXOSIP_MESSAGE_NEW:
			printf("EXOSIP_MESSAGE_NEW:");
			
			if (MSG_IS_OPTIONS (je->request)) //如果接受到的消息类型是OPTIONS
			{
				printf("options\n");
			}
			if (MSG_IS_MESSAGE (je->request))//如果接受到的消息类型是MESSAGE
			{
				osip_body_t *body;
				osip_message_get_body (je->request, 0, &body);
				printf ("message: %s\n", body->body);
			}
				eXosip_message_build_answer (je->tid, 200,&answer);//收到OPTIONS,必须回应200确认,否则无法callin,返回500错误
				eXosip_message_send_answer (je->tid, 200,answer);
			break;
        case EXOSIP_CALL_MESSAGE_NEW:
                        printf("EXOSIP_CALL_MESSAGE_NEW\n");
                        //osip_body_t *msg_body;
                        //osip_message_get_body (je->request, 0, &msg_body);
                        //printf ("call_message: %s\n", msg_body->body);
                        //eXosip_message_build_answer (je->tid, 200,&answer);//收到OPTIONS,必须回应200确认,否则无法callin,返回500错误
			//eXosip_message_send_answer (je->tid, 200,answer);
            break;
		case EXOSIP_CALL_MESSAGE_PROCEEDING:
			break;
		case EXOSIP_MESSAGE_ANSWERED:      /**< announce a 200ok  */
		case EXOSIP_MESSAGE_REDIRECTED:     /**< announce a failure. */
		case EXOSIP_MESSAGE_REQUESTFAILURE:  /**< announce a failure. */
		case EXOSIP_MESSAGE_SERVERFAILURE:  /**< announce a failure. */
		case EXOSIP_MESSAGE_GLOBALFAILURE:    /**< announce a failure. */
			break;
		default: 
			printf ("other response:type=%d\n",je->type);   
			break;  
      }  
      eXosip_event_free(je);  
  }  
}  
 
 
 
 
  
int   main (int argc, char *argv[])  
{  
 
	int regid=0;
	osip_message_t *reg = NULL;
	printf("r     向服务器注册\n");  
	printf("c     取消注册\n");  
	printf("i     发起呼叫请求\n");  
	printf("h     挂断\n");  
	printf("q     退出程序\n");  
	printf("s     执行方法INFO\n");  
	printf("m     执行方法MESSAGE\n");  


  if (eXosip_init() != 0)  {  
      printf ("Couldn't initialize eXosip!\n");  
      return -1;  
    }  

  if ( eXosip_listen_addr (IPPROTO_UDP, NULL, 5062, AF_INET, 0) != 0)  {  
      eXosip_quit ();  
      fprintf (stderr, "Couldn't initialize transport layer!\n");  
      return -1;  
    }  
    


  event_thread = osip_thread_create (20000, sipEventThread,NULL);
  if (event_thread==NULL){
      fprintf (stderr, "event_thread_create failed");
      exit (1);
    }
    else{
		 fprintf (stderr, "event_thread created!\n");
	}

   
    

  while (quit_flag)   {  
      printf ("please input the comand:\n");  
        
      scanf ("%c", &command);  
      getchar();  
        
      switch (command)  
    {  
	//--------------------注册----------------------------
    case 'r':  
		printf ("use [%s] start register!\n",fromuser); 
		eXosip_add_authentication_info (fromuser, userid, passwd, NULL, NULL);
		regid = eXosip_register_build_initial_register(fromuser, proxy,NULL, 3600, &reg);
		eXosip_register_send_register(regid, reg);
		quit_flag = 1;  
		break;  
	//--------------------呼叫----------------------------
    case 'i':/* INVITE */  
      i = eXosip_call_build_initial_invite (&invite, dest_call, source_call, NULL, "This si a call for a conversation");  
      if (i != 0)  
        {  
          printf ("Intial INVITE failed!\n");  
          break;  
        }  
      char localip2[128]; 
	  eXosip_guess_localip(AF_INET, localip2, 128); 
      snprintf (tmp, 4096,  
                    "v=0\r\n"
                    "o=- 0 0 IN IP4 %s\r\n"
                    "s=No Name\r\n"
                    "c=IN IP4 %s\r\n"
                    "t=0 0\r\n"
                    "m=audio 54000 RTP/AVP 8\r\n"
                    "a=rtpmap:8 PCMA/8000\r\n"
                    "m=video 54002 RTP/AVP 96\r\n"
                    "a=rtpmap:96 H264/90000\r\n",
                    localip2,localip2
            );  
      osip_message_set_body (invite, tmp, strlen(tmp));  
      osip_message_set_content_type (invite, "application/sdp");  
        
      eXosip_lock ();  
      i = eXosip_call_send_initial_invite (invite);  
      eXosip_unlock ();  
      break;  
	//--------------------挂断----------------------------
    case 'h':  
      printf ("Holded !\n");  
      eXosip_lock ();  
      eXosip_call_terminate (call_id, dialog_id);  
      eXosip_unlock ();  
      break;  
	//------------------注销------------------------
    case 't':
	videosendparam.send_quit=0;
	videorecvparam.recv_quit=0;
	break;
      case 'c':  
      printf ("This modal isn't commpleted!\n");  
/*//注销是时间为0
		eXosip_lock ();  
		i = eXosip_register_build_register (regid, 0, NULL);  
		if (i < 0)  
		{  
		eXosip_unlock ();  
		break; 
		}  
		eXosip_register_send_register (regid, reg);  
		eXosip_unlock ();  
*/
      break;  
      //-------------------消息---------------------
    case 's':  
      printf ("send info\n");  
      eXosip_call_build_info (dialog_id, &info);  
      snprintf (tmp , 4096,  "hello,aphero");  
      osip_message_set_body (info, tmp, strlen(tmp));  
      osip_message_set_content_type (info, "text/plain");  
      eXosip_call_send_request (dialog_id, info);  
      break;  
      //-----------------短信-------------------------
    case 'm':  
      printf ("send message\n");  
      eXosip_message_build_request (&message, "MESSAGE",  dest_call,source_call, NULL);  
      snprintf (tmp, 4096,"hello aphero");  
      osip_message_set_body (message, tmp, strlen(tmp));  
      osip_message_set_content_type (message, "text/plain");  
      eXosip_message_send_request (message);  
      break;  
	//--------------------退出---------------------
    case 'q': 
      quit_flag=0; 
      printf ("Exit the setup!\n");  
      break;  
    }  
    }  

	eXosip_quit ();  
  return (0);  
}  

16.video_rtp_recv.c


#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>
#include <assert.h>
#include <stdint.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <malloc.h>
#include "video_rtp_recv.h" 
#include "omx_decode.h"
#include "queue.h"

	
#define  RTP_HEADLEN 12
int  UnpackRTPH264(unsigned char *bufIn,int len,unsigned char *bufout,video_frame *videoframe)
{
int outlen=0;
     if  (len  <  RTP_HEADLEN){
         return  -1 ;
    }

    unsigned  char *src  =  (unsigned  char * )bufIn  +  RTP_HEADLEN;
    unsigned  char  head1  =   * src; // 获取第一个字节
    unsigned  char  nal  =  head1  &   0x1f ; // 获取FU indicator的类型域,
    unsigned  char  head2  =   * (src + 1 ); // 获取第二个字节
    unsigned  char  flag  =  head2  &   0xe0 ; // 获取FU header的前三位,判断当前是分包的开始、中间或结束
    unsigned  char  nal_fua  =  (head1  &   0xe0 )  |  (head2  &   0x1f ); // FU_A nal
	//这里可以获取CSRC/sequence number/timestamp/SSRC/CSRC等rtp头部信息.
	//是否可以定义rtp头部struct,memcopy缓存20个字节到rtp头部即可获取.
    videoframe->seq_no=bufIn[2] << 8|bufIn[3] << 0;//序号,用于判断rtp乱序
    videoframe->timestamp=bufIn[4] << 24|bufIn[5] << 16|bufIn[6] << 8|bufIn[7] << 0;//时间戳,送到解码中
    videoframe->flag= src[1]&0xe0;//判断包是开始 结束
    videoframe->nal= src[0]&0x1f;//是否是fu-a,还是单个包
    videoframe->frametype= src[1]&0x1f;//判断帧类型I,还有错误


	if  (nal == 0x1c ){//fu-a=28
		if  (flag == 0x80 ) // 开始=128
		{
			//printf("s ");
			bufout[0]=0x0;
			bufout[1]=0x0;
			bufout[2]=0x0;
			bufout[3]=0x1;
			bufout[4]=nal_fua;
			outlen  = len - RTP_HEADLEN -2+5;//-2跳过前2个字节,+5前面前导码和类型码,+5
			memcpy(bufout+5,src+2,outlen);
		}
		else   if (flag == 0x40 ) // 结束=64
		{
			//printf("e ");
			outlen  = len - RTP_HEADLEN -2 ;
			memcpy(bufout,src+2,len-RTP_HEADLEN-2);
		}
		else // 中间
		{
			//printf("c ");
			outlen  = len - RTP_HEADLEN -2 ;
			memcpy(bufout,src+2,len-RTP_HEADLEN-2);
		}
	}
	else {//当个包,1,7,8
		//printf("*[%d]* ",nal);
		bufout[0]=0x0;
		bufout[1]=0x0;
		bufout[2]=0x0;
		bufout[3]=0x1;
		memcpy(bufout+4,src,len-RTP_HEADLEN);
		outlen=len-RTP_HEADLEN+4;
	}
	return  outlen;
}





CircleQueue bufferqueue;
frame  in_avframe,out_avframe;
pthread_mutex_t mut;

void* dec_thread(void *arg){	
	struct video_param_recv *video_recv_param=arg;
	while((video_recv_param->recv_quit==1)){
		if(IsQueueEmpty(&bufferqueue))  	{continue;}
		if(bufferqueue.count<5){ usleep(1000);continue;}
		pthread_mutex_lock(&mut);
		out_avframe=DeQueue(&bufferqueue);//从队列中读取数据
		pthread_mutex_unlock(&mut);
		//printf("[DeQueue]:[%d-%d]=%2x %2x %2x %2x\n",bufferqueue.count,out_avframe.seqnum,out_avframe.data[4],out_avframe.data[5],out_avframe.data[6],out_avframe.data[7]);
		//printf("[DeQueue]:[%d-%d]=%s\n",bufferqueue.count,out_avframe.seqnum,out_avframe.data);
		//if((out_avframe.seqnum) % 30==0) 
		printf("[DeQueue]:[%d]:[%d]\n",bufferqueue.count,out_avframe.seqnum);
		if(omx_decode(out_avframe.data,out_avframe.len,out_avframe.timestamp)!=0) printf("omx_decode fail\n");
	}
	return NULL;
}


#define LUANXU 0 //乱序后重新排序
#define NOLUANXU 0//乱序不排序
void *video_recv(void *videorecvparam){
	unsigned char buffer[2048];
	unsigned char outbuffer[2048];
	unsigned char *in_buffer;//
	video_frame videoframe;

	unsigned short last_seq_no=0;//上一个包的序号
#if LUANXU
	int i;
	int count=0;
	unsigned short start_seq_no=0;//开始乱序的序号,eg.123546  =3
	#define  BUFCOUNT 20
	frame  luan_avframe[BUFCOUNT];
	//memset(&luan_avframe, 0, sizeof(luan_avframe)); 
	
#endif
	struct video_param_recv *video_recv_param=videorecvparam;
	memset(&videoframe, 0, sizeof(videoframe)); 
	int outbuffer_len;
	pthread_t decthread;
		
	if(omx_init()!=0){
		fprintf(stderr,RED"[%s]:" NONE"omx_init fail\n",__FILE__);
		return NULL;
	}
	
	InitQueue(&bufferqueue);
	//if(pthread_create(&decthread,NULL,dec_thread,videorecvparam)<0) printf ("Create dec pthread error!\n");	
	while((video_recv_param->recv_quit==1)){
		usleep(100);
		int recv_len;
		//outbuffer=(unsigned char *)malloc(2048);
		bzero(buffer, sizeof(buffer));
		recv_len = recv(video_recv_param->video_rtp_socket, buffer, sizeof(buffer),0);
		if(recv_len<0) 	continue; 
		outbuffer_len=UnpackRTPH264(buffer,recv_len,outbuffer,&videoframe);
		//fprintf(stderr,BLUE"[%s]:" NONE"seq_no[%d],flage[%d],video_recv_len[%d],outbuffer_len[%d]\n",__FILE__,videoframe.seq_no,videoframe.flag,recv_len,outbuffer_len);
		//printf("frame:seq_no[%d],nal[%d],type[%d],flage[%d]\n",videoframe.seq_no,videoframe.nal,videoframe.frametype,videoframe.flag);
		//in_buffer=(unsigned char *)malloc(outbuffer_len);//分配内存,保存到队列中去
		//memcpy(in_buffer,outbuffer,outbuffer_len);
#if LUANXU		
		//这里注意rtp乱序
		if((videoframe.seq_no!=0 && videoframe.seq_no!=(last_seq_no+1))){//乱序
			if(start_seq_no==0)start_seq_no=last_seq_no;
			 printf("rtp seq error:S[%d][%d]F[%d]C[%d]\n",start_seq_no,count,last_seq_no,videoframe.seq_no);
				luan_avframe[videoframe.seq_no-start_seq_no].data=in_buffer;
				luan_avframe[videoframe.seq_no-start_seq_no].len=outbuffer_len;
				luan_avframe[videoframe.seq_no-start_seq_no].timestamp=videoframe.timestamp;
				luan_avframe[videoframe.seq_no-start_seq_no].seqnum=videoframe.seq_no;
				last_seq_no=videoframe.seq_no;//保存上一个包的时间戳
				//count++;//乱包计数//注释掉,可以跳过乱包,不存入队列
				continue;
		 }else{//正确序
				if(start_seq_no!=0){
					for(i=0;i<count;i++){
						pthread_mutex_lock(&mut);
						//EnQueue(&bufferqueue,luan_avframe[i+1]);//将乱包重新排序后存入队列
						pthread_mutex_unlock(&mut);
						//printf("[EnQueue]:[%d-%d]=%2x %2x %2x %2x\n",bufferqueue.count,luan_avframe[i+1].seqnum,luan_avframe[i+1].data[4],luan_avframe[i+1].data[5],luan_avframe[i+1].data[6],luan_avframe[i+1].data[7]);
					}
					start_seq_no=0;
					count=0;
				}
				in_avframe.data=in_buffer;
				in_avframe.len=outbuffer_len;
				in_avframe.timestamp=videoframe.timestamp;
				in_avframe.seqnum=videoframe.seq_no;
			 if(!IsQueueFull(&bufferqueue)){ 
				 pthread_mutex_lock(&mut);
				 EnQueue(&bufferqueue,in_avframe);	
				 pthread_mutex_unlock(&mut);	
				// printf("[EnQueue]:[%d-%d]=%2x %2x %2x %2x\n",bufferqueue.count,in_avframe.seqnum,in_avframe.data[4],in_avframe.data[5],in_avframe.data[6],in_avframe.data[7]);
				 }
		 }
		last_seq_no=videoframe.seq_no;//保存上一个包的时间戳
		if(outbuffer_len==0) continue;
#endif
#if NOLUANXU
		if((videoframe.seq_no!=0 && videoframe.seq_no!=(last_seq_no+1))){//乱序
			 printf("rtp seq error:F[%d]C[%d]\n",last_seq_no,videoframe.seq_no);
		 }
		last_seq_no=videoframe.seq_no;//保存上一个包的时间戳
		in_avframe.data=in_buffer;
		in_avframe.len=outbuffer_len;
		in_avframe.timestamp=videoframe.timestamp;
		in_avframe.seqnum=videoframe.seq_no;
		
		if(!IsQueueFull(&bufferqueue)){
			pthread_mutex_lock(&mut);
			EnQueue(&bufferqueue,in_avframe);
			pthread_mutex_unlock(&mut);
		}
		//printf("[EnQueue]:[%d-%d]=%2x %2x %2x %2x\n",bufferqueue.count,in_avframe.seqnum,in_avframe.data[4],in_avframe.data[5],in_avframe.data[6],in_avframe.data[7]);
		//printf("[EnQueue]:[%d-%d]=%s\n",bufferqueue.count,in_avframe.seqnum,in_avframe.data);
#endif
		
		//直接解码
		if((videoframe.seq_no!=0 && videoframe.seq_no!=(last_seq_no+1))){//乱序
			printf("rtp seq error:F[%d]C[%d]\n",last_seq_no,videoframe.seq_no);
		}
		last_seq_no=videoframe.seq_no;//保存上一个包的时间戳
		if(omx_decode(outbuffer,outbuffer_len,videoframe.timestamp)!=0) printf("omx_decode fail\n");//直接解码
		
	}

	if(omx_deinit()!=0){ printf("omx_deinit fail\n");}
	close(video_recv_param->video_rtp_socket);
	return NULL;
}

17.video_rtp_recv.h

#define NONE                      "\033[m"  
#define RED                         "\033[1;31m"
#define GREEN                   "\033[1;32m" 
#define BLUE                       "\033[1;34m"

typedef struct video_param_recv
{
int    video_rtp_socket;
char *video_hw;
char *dest_ip ;
int      dest_port;
int      local_port;
int       width;
int       height;
int       fps;
int        bitrate;
int        recv_thread;
int         recv_quit;
int         send_thread;
int         send_quit;
} video_param_recv;



typedef struct video_frame
{
unsigned char nal;
unsigned char frametype;
unsigned char flag;
unsigned long timestamp;
unsigned short seq_no;
} video_frame;

void *video_recv(void *videorecvparam);

18.video_rtp_send.c


#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>
#include <assert.h>
#include <stdint.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "video_rtp_send.h"
#include "omx_decode.h"
#include "omxcam/omxcam.h"
#include "rtpavcsend.h"



struct sockaddr_in s_peer;
static RtpSend* rtpsock;
static int udpsock;

static int sock_create(const char* peer_addr, int port,int localport)
{
	int s;

	memset(&s_peer, 0, sizeof(s_peer));
	s_peer.sin_family = AF_INET;
	s_peer.sin_addr.s_addr = inet_addr(peer_addr);
	s_peer.sin_port = htons(port);
	if (s_peer.sin_addr.s_addr == 0 || s_peer.sin_addr.s_addr == 0xffffffff) {
		fprintf(stderr, "Invalid address(%s)\n", peer_addr);
		return -1;
	}
	if (port <= 0 || port > 65535) {
		fprintf(stderr, "Invalid port(%d)\n", port);
		return -1;
	}
	s = socket(AF_INET, SOCK_DGRAM|SOCK_NONBLOCK, 0);
	if (s < 0) { perror("socket"); return -1; }
	
	//端口复用
	int flag=1;
	if( setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int)) == -1)  {  
	fprintf(stderr, RED "[%s@%s,%d]:"NONE"socket setsockopt error\n", __func__, __FILE__, __LINE__);
	return -1;
	}  
	//绑定本地端口
	struct sockaddr_in local;  
	local.sin_family=AF_INET;  
	local.sin_port=htons(localport);            ///监听端口  
	local.sin_addr.s_addr=INADDR_ANY;       ///本机  
	if(bind(s,(struct sockaddr*)&local,sizeof(local))==-1) {
	fprintf(stderr, RED "[%s@%s,%d]:"NONE"udp port bind error\n",__func__, __FILE__, __LINE__);
	return -1;
	}
	return s;
}



void *video_send(void *videosendparam){

	video_param_send *video_send_param=videosendparam;
	//使用video_send_param->video_rtp_socket这个socket不发送数据,暂时还是再创建socket
	omxcam_video_settings_t videoset = {};

	//摄像头/编码器初始化
	omxcam_video_init(&videoset);
	//摄像头参数设置
	videoset.camera.width = video_send_param->width;
	videoset.camera.height = video_send_param->height;
	videoset.camera.framerate = video_send_param->fps;
	videoset.camera.rotation=180;
	//H264编码器设置
	videoset.h264.bitrate = (video_send_param->bitrate)*1000; //12Mbps
	videoset.h264.idr_period = 10;	//30 IDR间隔
	videoset.h264.inline_headers = OMXCAM_TRUE; // SPS/PPS头插入
	videoset.h264.profile=OMXCAM_H264_AVC_PROFILE_HIGH;//OMXCAM_H264_AVC_PROFILE_BASELINE;

	fprintf(stderr,BLUE"[%s]:" NONE"video_send_param:%s:%d\n", __FILE__,video_send_param->dest_ip, video_send_param->dest_port);
	fprintf(stderr,BLUE"[%s]:" NONE"camera_param:%dX%d,fps:%d,%d\n", __FILE__,video_send_param->width, video_send_param->height,video_send_param->fps,video_send_param->bitrate);
	//网络初始化
	udpsock = sock_create(video_send_param->dest_ip, video_send_param->dest_port,video_send_param->local_port);
	//udpsock = sock_create("192.168.1.114", 8888);//用于调试,可将视频发送到第三个设备上,比如电脑上vlc接收
	// RTP初始化
	rtpopen(&rtpsock, 10110825/*SSRC*/, 96/*payload_type*/, udpsock/*video_send_param->video_rtp_socket*/, &s_peer);

	// 开始发送数据---videoset.on_data = video_encoded;
	omxcam_video_start_npt(&videoset);
	omxcam_buffer_t buffer;
	while (video_send_param->send_quit==1){
		if (omxcam_video_read_npt (&buffer, 0)) return NULL;
		AvcAnalyzeAndSend(rtpsock, buffer.data, buffer.length);
		//printf("encoded len=%d\n",buffer.length);
	}
	
	omxcam_video_stop_npt();

	rtpclose(rtpsock);
	//fprintf(stderr,BLUE"[%s]:" NONE"video send thread exit\n", __func__, __FILE__);
	return NULL;
	}	


19.video_rtp_send.h

#define NONE                      "\033[m"  
#define RED                         "\033[1;31m"
#define GREEN                   "\033[1;32m" 
#define BLUE                       "\033[1;34m"

typedef struct video_param_send
{
int    video_rtp_socket;
char *video_hw;
char *dest_ip ;
int      dest_port;
int      local_port;
int       width;
int       height;
int       fps;
int        bitrate;
int        recv_thread;
int         recv_quit;
int         send_thread;
int         send_quit;
} video_param_send;

struct video_param_send *video_send_param;

void *video_send(void *videosendparam);

20.makefile

CC=cc
CCFLAGS=-g -Wall -ftree-vectorize -pipe -Wno-psabi -DOSIP_MT
CCFLAGS+=-DSTANDALONE -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -D_LINUX -fPIC -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -U_FORTIFY_SOURCE -Wall -g -DHAVE_LIBOPENMAX=2 -DOMX -DOMX_SKIP64BIT -ftree-vectorize -pipe -DUSE_EXTERNAL_OMX -DHAVE_LIBBCM_HOST -DUSE_EXTERNAL_LIBBCM_HOST -DUSE_VCHIQ_ARM -Wno-psabi
CCFLAGS+=-I/opt/vc/src/hello_pi/libs/ilclient -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux

LDFLAGS=-leXosip2 -losip2 -losipparser2  -lasound -lcamkit -lasound
LDFLAGS+=-L/opt/vc/lib/ -lGLESv2 -lEGL -lopenmaxil -lbcm_host -lvcos -lvchiq_arm  -lpthread  -lrt -lm -L/opt/vc/src/hello_pi/libs/ilclient -L/opt/vc/src/hello_pi/libs/vgfont

OBJS=sip.o omx_decode.o video_rtp_send.o video_rtp_recv.o audio_rtp_recv.o  audio_rtp_send.o queue.o g711.o g711codec.o rtpavcsend.o  omxcam/debug.o omxcam/video.o omxcam/h264.o omxcam/core.o omxcam/error.o omxcam/still.o omxcam/camera.o omxcam/dump_omx.o omxcam/version.o omxcam/event.o omxcam/jpeg.o omxcam/utils.o

TARGET=sipclient

all:: $(TARGET)

%.o: %.c
	@rm -f $@
	$(CC) $(CCFLAGS) $(LDFLAGS) -c $< -o $@ -Wno-deprecated-declarations

$(TARGET): $(OBJS)
	$(CC) $(CCFLAGS) -o $@ $(OBJS) $(LDFLAGS)
	
clean::
	rm -f $(OBJS) $(TARGET)
	



还有omxcam文件夹中的文件github中可以找到

camera.c  dump_omx.c  h264.c      omxcam.h          utils.c
core.c    error.c     internal.h  omxcam_version.h  version.c
debug.c   event.c     jpeg.c      still.c           video.c










  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值