树莓派sip视频电话-3:exosip2+硬件h264+g711编码初步实现

    之前使用python语音实现,但是python下的exosip2库部分功能不能实现,现改为c语音的exsip方式,初步实现sip视频电话功能。

    测试环境:树莓派------------elastix---------------ekiga(pc端)   视频为h.264 音频为G711u

    存在问题:1.源码中有几处告警未处理;2.线程终止没有实现,呼叫结束后,视频线程仍在发送(不能呼入两次),音频线程因对方端口关闭终止退出;3.接收视音频还没有实现,采用外部程序omxplayer 播放sdp文件方式实现。4.只做了呼入视频 音频发送,呼出未做。(休假快结束了,暂时就到这里了,有时间再完善!)

1.卸载exosip 4.1版本
    sudo apt-get remove libosip2-11 libosip2-dev libexosip2-11    libexosip2-dev
2.编译安装3.6版本

    libosip2-3.6.0  ./configure&&make&&sudo make install
    libexosip2-3.6.0  ./configure&&make&&sudo make install

3.安装编译视频编码动态链接库

    树莓派Camkit,源码百度查找,感谢作者!

4.编译sip源码
    (1)sip部分:使用了exsip2实现,开启了3个线程,事件处理,音频发送,视频发送3个线程。

    (2)音频部分:为g711U编码,在源码中可以改为g711a。

    (3)视频部分:使用了omx硬件编码方式,调用了牛人的Camkit源码,动态链接方式。

5.源代码:

(1)sip.c

#include <eXosip2/eXosip.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <netinet/in.h>  
#include <sys/socket.h>  
#include <sys/types.h>  
#include <pthread.h>
#include "audio_rtp.h"
#include "video_rtp.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; 

int call_id, dialog_id,calling ;  
int i,flag;  
int flag1 = 1;  
int id;  

struct osip_thread *event_thread;
struct osip_thread *audio_thread;
struct osip_thread *video_thread;
struct audio_param_t audioparam;
struct video_param_t videoparam;



char *identity = "sip:8006@192.168.1.200";  
char *registerer = "sip:192.168.1.200";  
char *source_call = "sip:8006@192.168.1.200";  
char *dest_call = "sip:8001@192.168.1.200";  

char command;  
char tmp[4096];  
char localip[128];  

 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---401 error!\n");
			eXosip_automatic_action(); //发送认证信息,完成注册
			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 54000 RTP/AVP 0\r\n"
					"a=rtpmap:0 PCMU/8000\r\n"
					"m=video 4002 RTP/AVP 96\r\n"
					"a=rtpmap:96 H264/90000\r\n"
					,localip, localip
			   );

			//设置回复的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 ();
     
			//显示出在sdp消息体中的 attribute 的内容,里面计划存放我们的信息
			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 ("%s : %s\n", at->a_att_field, at->a_att_value);//这里解释了为什么在SDP消息体中属性a里面存放必须是两列
			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(0,8)
			sdp_attribute_t *at;
			at = (sdp_attribute_t *) osip_list_get ((const osip_list_t *)&md_video_req->a_attributes, pos);
			//printf ("%s : %s\n", at->a_att_field, at->a_att_value);//这里解释了为什么在SDP消息体中属性a里面存放必须是两列
			printf("payload_str=%s,m_media=%s\n",payload_str,at->a_att_value);
			pos++;
			}
			printf("audio video port info:--------------\n");
			printf("conn_add=%s,audio_port=%s,video_port=%s\n",con_req->c_addr,md_audio_req->m_port,md_video_req->m_port);
			
			//audioparam.dest_ip = (char *)con_req->c_addr;有误
			audioparam.dest_port =atoi(md_audio_req->m_port);
			audioparam.dest_ip = "192.168.1.200";
			audioparam.audio_hw = "default";
			
			//videoparam.dest_ip = (char *)con_req->c_addr;有误
			videoparam.dest_ip = "192.168.1.200";
			videoparam.dest_port =atoi(md_video_req->m_port);
			videoparam.video_hw = "/dev/video0";
			
			sdp_message_free(remote_sdp);
			remote_sdp = NULL;
			break;  
		case EXOSIP_CALL_REINVITE:
			printf("REINVITE\n");
			break;
		case EXOSIP_CALL_PROCEEDING:  
			printf ("proceeding!\n");  
			break;  
		case EXOSIP_CALL_RINGING:  
			printf ("ringing!\n");  
			// call_id = je->cid;  
			// dialog_id = je->did;  
			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_CLOSED:  
			printf ("the call sid closed!\n");  
			//osip_thread_exit(audio_thread);//终止线程,未工作
			//osip_thread_exit(video_thread);
			break;  
		case EXOSIP_CALL_ACK:  
			printf ("ACK received!\n");  
			//获取到远程的sdp信息后,分别建立音频 视频2个线程
			
			//下面传递参数出错
			//printf("conn_add=%s,audio_port=%s,video_port=%s\n",con_req->c_addr,md_audio_req->m_port,md_video_req->m_port);
			//audio_thread = osip_thread_create (20000, audio_rtp , audido_hw , con_req->c_addr,md_audio_req->m_port);
			eXosip_lock();  
			printf("conn_add=%s,audio_port=%d\n",audioparam.dest_ip,audioparam.dest_port);
			audio_thread = osip_thread_create (20000, &audio_rtp , &audioparam);
			if (audio_thread==NULL){
				fprintf (stderr, "audio_thread_create failed");
				exit (1);
			}
			else{
				fprintf (stderr, "audio_thread created!\n");
			}
			 eXosip_unlock(); 
			 //视频线程
			 eXosip_lock();  
			printf("conn_add=%s,audio_port=%d\n",audioparam.dest_ip,audioparam.dest_port);
			video_thread = osip_thread_create (20000, &video_rtp,&videoparam);
			if (video_thread==NULL){
				fprintf (stderr, "video_thread_create failed");
				exit (1);
			}
			else{
				fprintf (stderr, "video_thread created!\n");
			}
			 eXosip_unlock(); 
			 
			break;  
		case EXOSIP_MESSAGE_NEW:
			printf("EXOSIP_MESSAGE_NEW!\n");
			
			if (MSG_IS_OPTIONS (je->request)) 
			{
				printf("options\n");//必须回应200确认,否则无法callin,返回500错误
				eXosip_message_build_answer (je->tid, 200,&answer);
				eXosip_message_send_answer (je->tid, 200,answer);
			}
			if (MSG_IS_MESSAGE (je->request))//如果接受到的消息类型是MESSAGE
			{
			osip_body_t *body;
			osip_message_get_body (je->request, 0, &body);
			printf ("I get the msg is: %s/n", body->body);
			//printf ("the cid is %s, did is %s/n", je->did, je->cid);
			//按照规则,需要回复200 OK信息
			eXosip_message_build_answer (je->tid, 200,&answer);
			eXosip_message_send_answer (je->tid, 200,answer);
			}
			break;
			
		default: 
			printf ("other response!\n");   
			break;  
      }  
      eXosip_event_free(je);  
  }  
}  
 
 
 
 
  
int   main (int argc, char *argv[])  
{  
 
	int regid=0;
	osip_message_t *reg = NULL;
	char fromuser[256];  
	char proxy[256];  

	sprintf(fromuser,"sip:%s@%s","8006","192.168.1.200");  
	sprintf(proxy,"sip:%s","192.168.1.200");  
 
  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, 5066, AF_INET, 0) != 0)  
    {  
      eXosip_quit ();  
      fprintf (stderr, "Couldn't initialize transport layer!\n");  
      return -1;  
    }  
    


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

   
    
  flag = 1;  
  while (flag)  
    {  
      printf ("please input the comand:\n");  
        
      scanf ("%c", &command);  
      getchar ();  
        
      switch (command)  
    {  
	//--------------------注册----------------------------
    case 'r':  
		printf ("start register!\n");  
		eXosip_add_authentication_info ("8006@192.168.1.200", "8006", "***************", NULL, NULL);
		regid = eXosip_register_build_initial_register(fromuser, proxy,NULL, 3600, &reg);
		eXosip_register_send_register(regid, reg);
		flag1 = 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;  
        }  
      snprintf (tmp, 4096,  
                    "v=0\r\n"
                    "o=- 0 0 IN IP4 192.168.1.132\r\n"
                    "s=No Name\r\n"
                    "c=IN IP4 192.168.1.132\r\n"
                    "t=0 0\r\n"
                    "m=audio 54000 RTP/AVP 8\r\n"
                    "a=rtpmap:8 PCMA/8000\r\n"
                    "m=video 4002 RTP/AVP 96\r\n"
                    "a=rtpmap:96 H264/90000\r\n"
            );  
      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 'c':  
      printf ("This modal isn't commpleted!\n");  
/*
		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':  
    //传输INFO方法  
      eXosip_call_build_info (dialog_id, &info);  
      snprintf (tmp , 4096,  "hello,aphero");  
      osip_message_set_body (info, tmp, strlen(tmp));  
      //格式可以任意设定,text/plain代表文本信息  
      osip_message_set_content_type (info, "text/plain");  
      eXosip_call_send_request (dialog_id, info);  
      break;  
      //-----------------短信-------------------------
    case 'm':  
    //传输MESSAGE方法,也就是即时消息,和INFO方法相比,我认为主要区别,是MESSAGE不用建立连接,直接传输信息,而INFO必须  
    //在建立INVITE的基础上传输。  
      printf ("the mothed :MESSAGE\n");  
      eXosip_message_build_request (&message, "MESSAGE", dest_call, source_call, NULL);  
      snprintf (tmp, 4096,              "hellor aphero");  
      osip_message_set_body (message, tmp, strlen(tmp));  
      //假设格式是xml  
      osip_message_set_content_type (message, "text/xml");  
      eXosip_message_send_request (message);  
      break;  
	//--------------------退出---------------------
    case 'q': 
     flag1=0; 
      eXosip_quit ();  
      printf ("Exit the setup!\n");  
      flag = 0;  
      break;  
    }  
    }  
  return (0);  
}  

(2)video_rtp.h 

数据结构为参数传递部分,很多设置没有传递,下步再继续。

typedef struct video_param_t
{
char *video_hw;
char *dest_ip ;
int      dest_port;
} video_param_t;

void video_rtp(struct video_param_t *videoparam) ;

(3)video_rtp.c

原作者的程序,稍作改动。

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <linux/videodev2.h>
#include <sys/time.h>
#include <pthread.h>
#include <assert.h>
#include <stdint.h>
#include "camkit.h"
#include "video_rtp.h"

#define MAX_RTP_SIZE 1420

int debug = 0;


void video_rtp(struct video_param_t *videoparam)
{
	
	printf("video_rtp:video_hw=%s,dest_ip=%s,dest_port=%d\n",videoparam->video_hw,videoparam->dest_ip,videoparam->dest_port);
	struct cap_handle *caphandle = NULL;
	struct cvt_handle *cvthandle = NULL;
	struct enc_handle *enchandle = NULL;
	struct pac_handle *pachandle = NULL;
	struct net_handle *nethandle = NULL;
	struct cap_param capp;
	struct cvt_param cvtp;
	struct enc_param encp;
	struct pac_param pacp;
	struct net_param netp;

	U32 vfmt = V4L2_PIX_FMT_YUYV;
	U32 ofmt = V4L2_PIX_FMT_YUV420;

	// set default values
	capp.dev_name = "/dev/video0";
	capp.width = 640;
	capp.height = 480;
	capp.pixfmt = vfmt;
	capp.rate = 15;

	cvtp.inwidth = 640;
	cvtp.inheight = 480;
	cvtp.inpixfmt = vfmt;
	cvtp.outwidth = 640;
	cvtp.outheight = 480;
	cvtp.outpixfmt = ofmt;

	encp.src_picwidth = 640;
	encp.src_picheight = 480;
	encp.enc_picwidth = 640;
	encp.enc_picheight = 480;
	encp.chroma_interleave = 0;
	encp.fps = 15;
	encp.gop = 12;
	encp.bitrate = 1000;

	pacp.max_pkt_len = 1400;
	pacp.ssrc = 1234;

	netp.serip = videoparam->dest_ip;//ip地址 
	netp.serport =videoparam->dest_port;//端口 
	netp.type = UDP;


	capp.width = cvtp.inwidth = cvtp.outwidth = encp.src_picwidth =encp.enc_picwidth = 640;
	capp.height = cvtp.inheight = cvtp.outheight =encp.src_picheight = encp.enc_picheight = 480;

	caphandle = capture_open(capp);
	if (!caphandle)
	{
		printf("--- Open capture failed\n");
		exit(1);
	}

		cvthandle = convert_open(cvtp);
		if (!cvthandle)
		{
			printf("--- Open convert failed\n");
			exit(1);
		}

		enchandle = encode_open(encp);
		if (!enchandle)
		{
			printf("--- Open encode failed\n");
			exit(1);
		}

		pachandle = pack_open(pacp);
		if (!pachandle)
		{
			printf("--- Open pack failed\n");
			exit(1);
		}

		if (netp.serip == NULL || netp.serport == -1)
		{
			printf("--- Server ip and port must be specified when using network\n");
			exit(1);
		}

		nethandle = net_open(netp);
		if (!nethandle)
		{
			printf("--- Open network failed\n");
			exit(1);
		}

	// start capture encode loop
	int ret;
	void *cap_buf, *cvt_buf, *hd_buf, *enc_buf;
	char *pac_buf = (char *) malloc(MAX_RTP_SIZE);
	int cap_len, cvt_len, hd_len, enc_len, pac_len;
	enum pic_t ptype;
	struct timeval ctime, ltime;
	unsigned long fps_counter = 0;
	int sec, usec;
	double stat_time = 0;

	capture_start(caphandle);		// !!! need to start capture stream!

	gettimeofday(<ime, NULL);
	while (1)
	{
		if (debug)		// print fps
		{
			gettimeofday(&ctime, NULL);
			sec = ctime.tv_sec - ltime.tv_sec;
			usec = ctime.tv_usec - ltime.tv_usec;
			if (usec < 0)
			{
				sec--;
				usec = usec + 1000000;
			}
			stat_time = (sec * 1000000) + usec;		// diff in microsecond

			if (stat_time >= 1000000)    // >= 1s
			{
				printf("\n*** FPS: %ld\n", fps_counter);

				fps_counter = 0;
				ltime = ctime;
			}
			fps_counter++;
		}

		ret = capture_get_data(caphandle, &cap_buf, &cap_len);
		if (ret != 0)
		{
			if (ret < 0)		// error
			{
				printf("--- capture_get_data failed\n");
				break;
			}
			else	// again
			{
				usleep(10000);
				continue;
			}
		}
		if (cap_len <= 0)
		{
			printf("!!! No capture data\n");
			continue;
		}
		if (debug)
			fputc('.', stdout);



		// convert
		if (capp.pixfmt == V4L2_PIX_FMT_YUV420)    // no need to convert
		{
			cvt_buf = cap_buf;
			cvt_len = cap_len;
		}
		else	// do convert: YUYV => YUV420
		{
			ret = convert_do(cvthandle, cap_buf, cap_len, &cvt_buf, &cvt_len);
			if (ret < 0)
			{
				printf("--- convert_do failed\n");
				break;
			}
			if (cvt_len <= 0)
			{
				printf("!!! No convert data\n");
				continue;
			}
		}
		if (debug)
			fputc('-', stdout);



		// encode
		// fetch h264 headers first!
		while ((ret = encode_get_headers(enchandle, &hd_buf, &hd_len, &ptype))
				!= 0)
		{
			if (debug)
				fputc('S', stdout);

			// pack headers
			pack_put(pachandle, hd_buf, hd_len);
			while (pack_get(pachandle, pac_buf, MAX_RTP_SIZE, &pac_len) == 1)
			{
				if (debug)
					fputc('#', stdout);

				// network
				ret = net_send(nethandle, pac_buf, pac_len);
				if (ret != pac_len)
				{
					printf("send pack failed, size: %d, err: %s\n", pac_len,
							strerror(errno));
				}
				if (debug)
					fputc('>', stdout);
			}
		}

		ret = encode_do(enchandle, cvt_buf, cvt_len, &enc_buf, &enc_len,
				&ptype);
		if (ret < 0)
		{
			printf("--- encode_do failed\n");
			break;
		}
		if (enc_len <= 0)
		{
			printf("!!! No encode data\n");
			continue;
		}

		if (debug)
		{
			char c;
			switch (ptype)
			{
				case PPS:
					c = 'S';
					break;
				case SPS:
					c = 'S';
					break;
				case I:
					c = 'I';
					break;
				case P:
					c = 'P';
					break;
				case B:
					c = 'B';
					break;
				default:
					c = 'N';
					break;
			}

			fputc(c, stdout);
		}

		// pack
		pack_put(pachandle, enc_buf, enc_len);
		while (pack_get(pachandle, pac_buf, MAX_RTP_SIZE, &pac_len) == 1)
		{
			if (debug)
				fputc('#', stdout);

			// network
			ret = net_send(nethandle, pac_buf, pac_len);
			if (ret != pac_len)
			{
				printf("send pack failed, size: %d, err: %s\n", pac_len,
						strerror(errno));
			}
			if (debug)
				fputc('>', stdout);
		}
	}
	capture_stop(caphandle);

	free(pac_buf);
	net_close(nethandle);
	pack_close(pachandle);
	encode_close(enchandle);
	convert_close(cvthandle);
	capture_close(caphandle);

	exit(0);
}

(4)audio_rtp.h

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_t
{
char *audio_hw;
char *dest_ip ;
int      dest_port;
} audio_param_t;

void audio_rtp(struct audio_param_t *audioparam) ;

(5)audio_rtp.c

/*

This example reads from the default PCM device
and G711 code and rtp send.

*/
#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.h"

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

/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API


void audio_rtp(struct audio_param_t *audioparam) {
	
	//char *audio_hw, char *dest_ip, int dest_port
	
	printf("audio_rtp:audio_hw=%s,dest_ip=%s,dest_port=%d\n",audioparam->audio_hw,audioparam->dest_ip,audioparam->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, audioparam->audio_hw , SND_PCM_STREAM_CAPTURE, 0);
	if (err < 0) {
		fprintf(stderr,"unable to open pcm device: %s\n",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, "Can not configure this PCM device: %s\n",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,   "Failed to set PCM device to interleaved: %s\n", 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,"Failed to set PCM device to 16-bit signed PCM: %s\n", snd_strerror(err));
		    exit(1);
		}
	/* One channels (mono) */
	err=snd_pcm_hw_params_set_channels(handle, params, CHANNELS);
		if (err < 0) {
		    fprintf(stderr, "Failed to set PCM device to mono: %s\n",snd_strerror(err));
		    exit(1);
		}
	/* 8000 bits/second sampling rate (CD quality) */
	val = SAMPLE_RATE;//这里修改为8000
	err=snd_pcm_hw_params_set_rate_near(handle, params,&val, &dir);
		if (err < 0) {
		    fprintf(stderr, "Failed to set PCM device to sample rate =%d: %s\n",val,snd_strerror(err));
		    exit(1);
		}

	/* 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, "Failed to set PCM device to buffer time =%d: %s\n",  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, "Failed to set PCM device to period time =%d: %s\n",period_time,snd_strerror(err));
		    exit(1);
		}

	/* Write the parameters to the driver */
	err = snd_pcm_hw_params(handle, params);
		if (err < 0) {
			fprintf(stderr,"unable to set hw parameters: %s\n",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);
	
	printf("read buffer size = %d\n",size);
	printf("period size = %d frames\n", (int)frames);

	/*print alsa config parameter*/
	snd_pcm_hw_params_get_period_time(params,&val, &dir);
	printf("period time is: %d\n",val);
	snd_pcm_hw_params_get_buffer_time(params, &val, &dir);
	printf("buffer time = %d us\n", val);
	snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &val);
	printf("buffer size = %d frames\n", val);
	snd_pcm_hw_params_get_periods(params, &val, &dir);
	printf("periods per buffer = %d frames\n", val);
	


    int M_bit=1;

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

    int    rtp_socket;
    struct sockaddr_in server;
    int len = sizeof(server);
    unsigned int timestamp_increse = 0,ts_current = 0;
    timestamp_increse = 160;

    server.sin_family = AF_INET;
    server.sin_port = htons(audioparam->dest_port);
    server.sin_addr.s_addr = inet_addr(audioparam->dest_ip);
    rtp_socket = socket(AF_INET,SOCK_DGRAM,0);
    connect(rtp_socket, (struct sockaddr *)&server, len);

	while (1) {
		//1.采集
		rc = snd_pcm_readi(handle, buffer, frames);//采集音频数据
		if (rc == -EPIPE) {
			fprintf(stderr, "overrun occurred\n");
			err=snd_pcm_prepare(handle);
			if( err <0){
				fprintf(stderr, "Failed to recover form overrun : %s\n",
				snd_strerror(err));
				exit(1);
			}
		}
		else if (rc < 0) {
			fprintf(stderr,"error from read: %s\n",snd_strerror(rc));
			exit(1);
		} 
		else if (rc != (int)frames) {
			fprintf(stderr, "short read, read %d frames\n", rc);
		}
		
		//2.编码
		rc = PCM2G711u( (char *)buffer, (char *)&sendbuf[12], size, 0 );//pcm转g711a
		if(rc<0)  fprintf(stderr,"PCM2G711u error:rc=%d\n",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( rtp_socket, sendbuf, rc+12, 0 );//开始发送rtp包,+12是rtp的包头+g711荷载
		if(rc<0) {
			fprintf(stderr,"net send error=%d\n",rc);
			break;
		}
        memset(sendbuf,0,1500);//清空sendbuf;此时会将上次的时间戳清空,因此需要ts_current来保存上次的时间戳值
	}

	snd_pcm_drain(handle);
	snd_pcm_close(handle);
	free(buffer);
}
(6)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 */
(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)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;	
}

(9)makefile

CC=gcc
CCFLAGS=-g -Wall
LDFLAGS=-leXosip2 -losip2 -losipparser2 -lpthread -lasound -lcamkit

all:sip

sip:sip.c
	$(CC) sip.c video_rtp.c audio_rtp.c g711.c g711codec.c  $(CCFLAGS) $(LDFLAGS) -o sip

clean:
	rm sip

6.运行测试

(1)运行后,r 注册服务器;(2)ekiga呼入,自动应答建立链接。

aphero@aphero-desktop:~/project$ ./sip 
r     向服务器注册
c     取消注册
i     发起呼叫请求
h     挂断
q     退出程序
s     执行方法INFO
m     执行方法MESSAGE
event_thread created!
please input the comand:
EXOSIP_MESSAGE_NEW!
EXOSIP_MESSAGE_NEW!
r
start register!
please input the comand:
EXOSIP_REGISTRATION_FAILURE---401 error!
registrered successfully
EXOSIP_MESSAGE_NEW!
options
EXOSIP_MESSAGE_NEW!
EXOSIP_MESSAGE_NEW!
Received a INVITE msg from 192.168.1.101:5066, UserName is 8006, password is (null)
send 200 over!
the INFO is :
remote_sdp_str=======================
v=0
o=root 120365001 120365001 IN IP4 192.168.1.200
s=Asterisk PBX 11.13.0
c=IN IP4 192.168.1.200
b=CT:8096
t=0 0
m=audio 19874 RTP/AVP 0 8 3 9 101
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:3 GSM/8000
a=rtpmap:9 G722/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-16
a=ptime:20
a=sendrecv
m=video 11520 RTP/AVP 99 34
a=rtpmap:99 H264/90000
a=fmtp:99 profile-level-id=42801E;max-mbps=190080;max-fs=6336;packetization-mode=1
a=rtpmap:34 H263/90000
a=fmtp:34 F=0;I=0;J=0;T=0;K=0;N=0;BPP=0;HRD=0
a=sendrecv
a=nortpproxy:yes

audio info:----------------
payload_str=0,m_media=0 PCMU/8000
payload_str=8,m_media=8 PCMA/8000
payload_str=3,m_media=3 GSM/8000
payload_str=9,m_media=9 G722/8000
payload_str=101,m_media=101 telephone-event/8000
video info:----------------
payload_str=99,m_media=99 H264/90000
payload_str=34,m_media=99 profile-level-id=42801E;max-mbps=190080;max-fs=6336;packetization-mode=1
audio video port info:--------------
conn_add=192.168.1.200,audio_port=19874,video_port=11520
ACK received!
conn_add=192.168.1.200,audio_port=19874
audio_thread created!
audio_rtp:audio_hw=hw:1,0,dest_ip=192.168.1.200,dest_port=19874
conn_add=192.168.1.200,audio_port=19874
video_thread created!
video_rtp:video_hw=/dev/video0,dest_ip=192.168.1.200,dest_port=11520
+++ Capture opened
!!! Set crop to (0, 0, 640, 480) failed. Don't panic, not all capture device support crop!
!!! VIDIOC_S_INPUT failed
+++ Device initialized
+++ Capture Opened
+++ Convert Opened
read buffer size = 320
period size = 160 frames
period time is: 20000
buffer time = 80000 us
buffer size = 640 frames
periods per buffer = 4 frames
[libx264 @ 0x73d13830] using cpu capabilities: ARMv6 NEON
[libx264 @ 0x73d13830] profile Main, level 2.2
+++ Encode Opened
+++ Pack Opened
+++ Network Opened
+++ Capture Started
!!! encoded frame delayed!
!!! No encode data
other response!
other response!
other response!
other response!
other response!
other response!
other response!
other response!
other response!
the call sid closed! 
net send error=-1
send pack failed, size: 157, err: Connection refused
send pack failed, size: 1193, err: Connection refused
^C

问题:
the call sid closed! //呼叫结束
net send error=-1  //音频线程出错退出
send pack failed, size: 157, err: Connection refused//视频线程远程端口被关闭,连接拒绝。
send pack failed, size: 1193, err: Connection refused





  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值