socket应用实例之RTP音频流传输

套接字的特性由3个属性确定,它们分别是:域、端口号、协议类型。
(1)套接字的域
它指定套接字通信中使用的网络介质,最常见的套接字域有两种:
一是AF_INET,它指的是Internet网络。当客户使用套接字进行跨网络的连接时,它就需要用到服务器计算机的IP地址和端口来指定一台联网机器上的某个特定服务,所以在使用socket作为通信的终点,服务器应用程序必须在开始通信之前绑定一个端口,服务器在指定的端口等待客户的连接。
另一个域AF_UNIX,表示UNIX文件系统,它就是文件输入/输出,而它的地址就是文件名。
(2)套接字的端口号
每一个基于TCP/IP网络通讯的程序(进程)都被赋予了唯一的端口和端口号,端口是一个信息缓冲区,用于保留Socket中的输入/输出信息,端口号是一个16位无符号整数,范围是0-65535,以区别主机上的每一个程序(端口号就像房屋中的房间号),低于256的端口号保留给标准应用程序,比如pop3的端口号就是110,每一个套接字都组合进了IP地址、端口,这样形成的整体就可以区别每一个套接字。
(3)套接字协议类型
因特网提供三种通信机制,
一是流套接字SOCK_STREAM:流套接字在域中通过TCP/IP连接实现,同时也是AF_UNIX中常用的套接字类型。流套接字提供的是一个有序、可靠、双向字节流的连接,因此发送的数据可以确保不会丢失、重复或乱序到达,而且它还有一定的出错后重新发送的机制。
二是数据报套接字SOCK_DGRAM:它不需要建立连接和维持一个连接,它们在域中通常是通过UDP/IP协议实现的。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较高,因为它并一需要总是要建立和维持一个连接。
三是原始套接字:原始套接字允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW。

                                      
 

                                        

RTP音频流传输的实现:

发送端:

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

#define TS_PACKET_SIZE 188
#define MTU 1500


struct rtp_header{
	unsigned char cc:4;
	unsigned char x:1;  
	unsigned char p:1;  
	unsigned char v:2;  

	unsigned char pt:7;  
	unsigned char m:1;  

	unsigned short sequence_number;  
	unsigned int timestamp;  
	unsigned int ssrc;  
};  

void init_rtp_header(struct rtp_header *h){  
	h->v = 2;  
	h->p = 0;  
	h->x = 0;  
	h->cc = 0;  
	h->m = 0;  
	h->pt = 33;  
	h->sequence_number =0;  
	h->timestamp = 123;  
	h->ssrc =0;  
}  


void sequence_number_increase(struct rtp_header *header){  
	unsigned short sequence = ntohs(header->sequence_number);  
	sequence++;  
	header->sequence_number = htons(sequence);  
} 



int main(int argc,char *argv[]){  
	char buf[MTU];  
	unsigned int count = 0;  
	if(argc < 2){
		printf(" need wav path \n");
		return -1;
	}

	// Init RTP Header  
	init_rtp_header((struct rtp_header*)buf);  
	count = sizeof(struct rtp_header);  

	// Init socket  
	int sock = socket(AF_INET, SOCK_DGRAM, 0);  
	struct sockaddr_in dest_addr;  

	dest_addr.sin_family=AF_INET;  
	dest_addr.sin_port = htons(5003);  
	dest_addr.sin_addr.s_addr =inet_addr("10.46.169.189");

	//	dest_addr.sin_addr.s_addr =INADDR_ANY;
	bzero(&(dest_addr.sin_zero),8);  

	// Open TS file  
	FILE *ts_file = fopen(argv[1], "r+"); 
        char h[44];
	fread(h,1,44,ts_file);
        int n=0;
	while(!feof(ts_file)){  
		int read_len = fread(buf+count, 1, TS_PACKET_SIZE, ts_file);  
		//	if(*(buf+count) != 0x47){  
		//		fprintf(stderr, "Bad sync header!\n");  
		//		continue;  
		//	}  
		count += read_len;  

		//	printf("count = %d\n",count);
		if (count + TS_PACKET_SIZE > MTU){// We should send  
			printf("haha_count = %d\n",count);
			sequence_number_increase((struct rtp_header*)buf);  
			sendto(sock, buf, count, 0, (const struct sockaddr*)&dest_addr, sizeof(dest_addr));  
			count = sizeof(struct rtp_header);  
			usleep(10000);  
	       n++;	
		} 
	}  
	printf("numofcount:%d\n",n*1316);

	fclose(ts_file); 
}

接收端:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/file.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <string.h>
#include <stdarg.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <signal.h>
#include <sys/wait.h>
#include <regex.h> 

struct rtp_header{  
    unsigned char cc:4;  
    unsigned char x:1;  
    unsigned char p:1;  
    unsigned char v:2;       
    unsigned char pt:7;  
    unsigned char m:1;  
    unsigned short sequence_number;  
    unsigned int timestamp;  
    unsigned int ssrc;  
};  

void main(int argc, char **argv)  
{  
  
    int socka;       
    int nPortA = 8000;  
    fd_set rfd;     // 读描述符集  
    struct timeval timeout;    // 定时变量  
    struct sockaddr_in addr; // 告诉sock 应该在什么地方licence  
    char recv_buf[1500];    // 接收缓冲区  
      
    int nRecLen; // 客户端地址长度!!!!!!  
      
    struct sockaddr_in cli;    // 客户端地址  
    int nRet; // select返回值  
 
    socka = socket(AF_INET, SOCK_DGRAM, 0); // 创建数据报socka  
    if (socka == -1)  
    {  
        printf("socket()/n");  
        return;  
    }  

    memset(&addr, 0, sizeof(addr));  
      
    addr.sin_family = AF_INET;   // IP协议  
    addr.sin_port = htons(nPortA); // 端口  
    addr.sin_addr.s_addr = htonl(INADDR_ANY); // 在本机的所有ip上开始监听  
    if (bind(socka, (struct sockaddr*)&addr, sizeof(addr)) == -1)// bind socka  
    {  
        printf("bind()/n");  
        return;  
    }  
      
 
    // 设置超时时间为6s  
    timeout.tv_sec = 6;   
    timeout.tv_usec = 0;  
      
    memset(recv_buf, 0, sizeof(recv_buf)); // 清空接收缓冲区  
    while (1)  
    {  
        FD_ZERO(&rfd); // 在使用之前总是要清空  
          
        // 开始使用select  
        FD_SET(socka, &rfd); // 把socka放入要测试的描述符集中  
  
          
        nRet = select(socka+1, &rfd, NULL, NULL, &timeout);// 检测是否有套接口是否可读  
        if (nRet == -1)     
        {  
            printf("select()\n");  
            return;  
        }  
        else if (nRet == 0) // 超时  
        {  
            //printf("timeout\n");  
			continue; 
        }  
        else    // 检测到有套接口可读  
        {  
            if (FD_ISSET(socka, &rfd))  // socka可读  
            {  
                nRecLen = sizeof(cli);  
                int nRecEcho = recvfrom(socka, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&cli, &nRecLen);  
                if (nRecEcho == -1)  
                {  
                    printf("recvfrom()\n");  
                    break;  
                }
				
                struct rtp_header *p = (struct rtp_header *)recv_buf;
				printf("v = %d\n",p->v);
				printf("p = %d\n",p->p);
				printf("x = %d\n",p->x);
				printf("cc = %d\n",p->cc);
				printf("sequence_number = %d\n",ntohs(p->sequence_number));
				printf("ssrc = %d\n",ntohs(p->ssrc));
            }  

        }  
    }  
}  

另附一基于socket的聊天程序实现:

socket实例C语言:一个简单的聊天程序https://www.cnblogs.com/liushao/p/6375377.html

  • 4
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
实现UDP接收rtp音频,可以使用Java的DatagramSocket类来进行网络数据包的接收,解析rtp音频可以参考RFC3550中的相关规范。将PCMA/PCMU格式音频数据转换成PCM格式,可以使用Java的AudioFormat类和AudioInputStream类来进行音频格式的转换。具体实现的代码如下: ```java import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.Arrays; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.TargetDataLine; public class RTPAudioReceiver { private static final int RTP_HEADER_SIZE = 12; private static final int UDP_PACKET_SIZE = 1500; private static final int AUDIO_SAMPLE_RATE = 8000; private static final int AUDIO_SAMPLE_SIZE_IN_BITS = 16; private static final int AUDIO_CHANNELS = 1; private static final boolean AUDIO_SIGNED = true; private static final boolean AUDIO_BIG_ENDIAN = true; private DatagramSocket socket; private InetAddress remoteAddress; private int remotePort; private AudioFormat audioFormat; private DataLine.Info dataLineInfo; private TargetDataLine targetDataLine; public RTPAudioReceiver(String remoteIP, int remotePort) throws IOException { this.remoteAddress = InetAddress.getByName(remoteIP); this.remotePort = remotePort; this.socket = new DatagramSocket(); this.audioFormat = new AudioFormat( AUDIO_SAMPLE_RATE, AUDIO_SAMPLE_SIZE_IN_BITS, AUDIO_CHANNELS, AUDIO_SIGNED, AUDIO_BIG_ENDIAN); this.dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat); this.targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo); } public void start() throws Exception { targetDataLine.open(audioFormat); targetDataLine.start(); byte[] buffer = new byte[UDP_PACKET_SIZE]; while (true) { DatagramPacket packet = new DatagramPacket(buffer, buffer.length, remoteAddress, remotePort); socket.receive(packet); byte[] rtpPacket = Arrays.copyOf(packet.getData(), packet.getLength()); byte[] pcmData = decodeRTP(rtpPacket); if (pcmData != null) { playAudio(pcmData); } } } private byte[] decodeRTP(byte[] rtpPacket) { int payloadType = (rtpPacket[1] & 0xFF) & 0x7F; if (payloadType != 0) { // 不支持的payload type return null; } int sequenceNumber = ((rtpPacket[2] & 0xFF) << 8) | (rtpPacket[3] & 0xFF); int timestamp = ((rtpPacket[4] & 0xFF) << 24) | ((rtpPacket[5] & 0xFF) << 16) | ((rtpPacket[6] & 0xFF) << 8) | (rtpPacket[7] & 0xFF); int ssrc = ((rtpPacket[8] & 0xFF) << 24) | ((rtpPacket[9] & 0xFF) << 16) | ((rtpPacket[10] & 0xFF) << 8) | (rtpPacket[11] & 0xFF); byte[] payload = Arrays.copyOfRange(rtpPacket, RTP_HEADER_SIZE, rtpPacket.length); if (payload.length == 0) { return null; } byte[] pcmData = decodePayload(payload); return pcmData; } private byte[] decodePayload(byte[] payload) { int numSamples = payload.length; byte[] pcmData = new byte[numSamples * 2]; for (int i = 0; i < numSamples; i++) { int pcmValue = ((int) payload[i]) << 8; pcmData[i * 2] = (byte) (pcmValue & 0xFF); pcmData[i * 2 + 1] = (byte) ((pcmValue >> 8) & 0xFF); } return pcmData; } private void playAudio(byte[] pcmData) { AudioInputStream audioInputStream = new AudioInputStream( new ByteArrayInputStream(pcmData), audioFormat, pcmData.length / audioFormat.getFrameSize()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, baos); } catch (IOException e) { e.printStackTrace(); } byte[] audioBytes = baos.toByteArray(); try { baos.close(); audioInputStream.close(); } catch (IOException e) { e.printStackTrace(); } targetDataLine.write(audioBytes, 0, audioBytes.length); } public static void main(String[] args) throws Exception { RTPAudioReceiver receiver = new RTPAudioReceiver("127.0.0.1", 12345); receiver.start(); } } ``` 调用示例: ```java RTPAudioReceiver receiver = new RTPAudioReceiver("127.0.0.1", 12345); receiver.start(); ``` 其中,参数"127.0.0.1"表示远程IP地址,参数12345表示远程端口号。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值