RFC3550 RTP:http://www.networksorcery.com/enp/rfc/rfc3550.txt
音频资源
g711a音频资源链接
测试音频: 麻雀 音频格式:PCMA 声道数:1 采样率:8000HZ 码率:64kbps
如何计算打包发送间隔、打包字节数
音频的帧率 fps = 20
采样率 sample_rate = 8000 HZ
码率 bitrate = 64000 bps
打包发送间隔 send_interval = 1 / 20 = 0.05s = 50000us
每次打包需要音频数据长度 audio_need_len = 64000 bps * 0.05s = 3200 bit = 400 bytes
udp发送数据长度 send_len = (rtp包头12 bytes )+ audio_need_len
C++ RTP打包代码
//rtp.h
#ifndef MMSER_RTP_H
#define MMSER_RTP_H
#include <iostream>
#include <vector>
enum eRtpPayloadType
{
RTP_PAYLOAD_TYPE_PCMU = 0, // g711u
RTP_PAYLOAD_TYPE_PCMA = 8, // g711a
RTP_PAYLOAD_TYPE_JPEG = 26,
RTP_PAYLOAD_TYPE_H264 = 96,
RTP_PAYLOAD_TYPE_H265 = 97,
RTP_PAYLOAD_TYPE_OPUS = 98,
RTP_PAYLOAD_TYPE_AAC = 99,
RTP_PAYLOAD_TYPE_G726 = 100,
RTP_PAYLOAD_TYPE_G726_16 = 101,
RTP_PAYLOAD_TYPE_G726_24 = 102,
RTP_PAYLOAD_TYPE_G726_32 = 103,
RTP_PAYLOAD_TYPE_G726_40 = 104,
RTP_PAYLOAD_TYPE_SPEEX = 105,
};
class RtpPacket
{
public:
//V(version):2 bits,RTP的版本,这里统一为2
int8_t version;
//P(padding):1 bit,如果置1,在packet的末尾被填充,填充有时是方便一些针对固定长度的算法的封装
int8_t padding;
//X(extension):1 bit,如果置1,在RTP Header会跟着一个header extension
int8_t extension;
//CC(CSRC count): 4 bits,表示头部后 特约信源 的个数
int8_t csrc_count;
//M(marker): 1 bit,不同的有效载荷有不同的含义,marker=1; 对于视频,标记一帧的结束;对于音频,标记会话的开始。
int8_t marker;
//PT(playload type): 7 bits,表示所传输的多媒体的类型,
int8_t playload_type;
//sequence number: 16 bits,每个RTP packet的sequence number会自动加一,以便接收端检测丢包情况
uint16_t sequence_number;
//timestamp: 32 bits,时间戳
uint32_t timestamp;
//SSRC: 32 bits,同步源的id,每两个同步源的id不能相同
uint32_t ssrc;
//CSRC: CC指定,每个CSRC标识符占32位,可以有0~15个
std::vector<uint32_t> csrc;
public:
RtpPacket(uint32_t ssrc_val);
virtual ~RtpPacket();
/**
* rtp 打包 g711a方法: | 12bytes RTP头 | G711 负载 |
* @param[in][out] payload 输入的负载数据,添加rtp头后返回
* @param[in][out] len 输入的负载数据长度,添加rtp头后返回新的长度
*/
void packet_g711a(uint8_t* payload, int& len);
};
#endif //MMSER_RTP_H
//rtp.cpp
#include <cstring>
#include "rtp.h"
RtpPacket::RtpPacket(uint32_t ssrc_val)
{
version = 2;
padding = 0;
extension = 0;
csrc_count = 0;
marker = 1;
playload_type = 0;
sequence_number = 0;
timestamp = 0;
ssrc = ssrc_val;
}
RtpPacket::~RtpPacket()
{
}
void RtpPacket::packet_g711a(uint8_t* payload, int& len)
{
playload_type = eRtpPayloadType::RTP_PAYLOAD_TYPE_PCMA;
sequence_number++;
timestamp += len;
uint8_t* start = payload;
int rtp_head_len = 12;
memmove(payload + rtp_head_len, payload, len);
len += rtp_head_len;
*payload++ = (version << 6) + (padding << 5) + (extension << 4) + csrc_count;
*payload++ = (marker << 7) + playload_type;
*payload++ = sequence_number / (0xff + 1);
*payload++ = sequence_number % (0xff + 1);
*payload++ = ( timestamp / (0xffff + 1) ) / (0xff + 1);
*payload++ = ( timestamp / (0xffff + 1) ) % (0xff + 1);
*payload++ = ( timestamp % (0xffff + 1) ) / (0xff + 1);
*payload++ = ( timestamp % (0xffff + 1) ) % (0xff + 1);
*payload++ = ( ssrc / (0xffff + 1) ) / (0xff + 1);
*payload++ = ( ssrc / (0xffff + 1) ) % (0xff + 1);
*payload++ = ( ssrc % (0xffff + 1) ) / (0xff + 1);
*payload = ( ssrc % (0xffff + 1) ) % (0xff + 1);
payload = start;
}
JAVA RTP打包代码
#目录结构
com/RtpPacket.java
main.java
mq.g711a
//RtpPacket.java
package com;
import java.util.Arrays;
class RtpPayloadType
{
public static int RTP_PAYLOAD_TYPE_PCMU = 0;
public static int RTP_PAYLOAD_TYPE_PCMA = 8;
}
public class RtpPacket
{
private byte version = 2;
private byte padding = 0;
private byte extension = 0;
private byte csrc_count = 0;
private byte marker = 1;
private byte playload_type = 0;
private int sequence_number = 0;
private long timestamp = 0;
private long ssrc = 0;
public RtpPacket(long ssrcVal) {
ssrc = ssrcVal;
}
public byte[] packet_g711a(byte[] audioData, int audioLen) {
int rtp_head_len = 12;
byte[] payload = new byte[rtp_head_len + audioLen];
playload_type = (byte)RtpPayloadType.RTP_PAYLOAD_TYPE_PCMA;
sequence_number++;
timestamp += audioLen;
payload[0] = (byte)( (version << 6) + (padding << 5) + (extension << 4) + csrc_count );
payload[1] = (byte)( (marker << 7) + playload_type );
payload[2] = (byte)( sequence_number / (0xff + 1) );
payload[3] = (byte)( sequence_number % (0xff + 1) );
payload[4] = (byte)( ( timestamp / (0xffff + 1) ) / (0xff + 1) );
payload[5] = (byte)( ( timestamp / (0xffff + 1) ) % (0xff + 1) );
payload[6] = (byte)( ( timestamp % (0xffff + 1) ) / (0xff + 1) );
payload[7] = (byte)( ( timestamp % (0xffff + 1) ) % (0xff + 1) );
payload[8] = (byte)( ( ssrc / (0xffff + 1) ) / (0xff + 1) );
payload[9] = (byte)( ( ssrc / (0xffff + 1) ) % (0xff + 1) );
payload[10] = (byte)( ( ssrc % (0xffff + 1) ) / (0xff + 1) );
payload[11] = (byte)( ( ssrc % (0xffff + 1) ) % (0xff + 1) );
System.arraycopy(audioData, 0, payload, rtp_head_len, audioLen);
return payload;
}
}
// main.java
import java.util.Arrays;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import com.RtpPacket;
public class main extends Thread {
public static void main(String[] args) throws IOException {
long ssrc = 255;
int send_interval_ms = 50;
int audio_need_len = 400;
int localPort = 56200;
int peerPort = 15062;
String peerIP = "192.168.1.101";
DatagramSocket ds = null;
InetAddress peerAddress = null;
InputStream audio_stream = new FileInputStream("./mq.g711a");
try {
ds = new DatagramSocket(localPort);
} catch (SocketException e) {
e.printStackTrace();
System.exit(1);
}
try {
peerAddress = InetAddress.getByName(peerIP);
} catch (UnknownHostException e) {
e.printStackTrace();
System.exit(1);
}
RtpPacket rtp = new RtpPacket(ssrc);
byte[] audio_data = new byte[audio_need_len];
while (audio_stream.read(audio_data) == audio_need_len) {
byte[] payload = rtp.packet_g711a(audio_data, audio_data.length);
DatagramPacket dp = new DatagramPacket(payload, payload.length, peerAddress, peerPort);
ds.send(dp);
//System.out.println(Arrays.toString(payload));
try {
Thread.sleep(send_interval_ms);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
ds.close();
}
}
# 编译
javac ./com/RtpPacket.java
javac ./main.java
# 执行
java main
测试音频使用工具 vlc 或 ffplay
ffplay -i rtp://192.168.1.101:15062