rtp传输音视频(纯c代码)
参考链接: 1. PES,TS,PS,RTP等流的打包格式解析之RTP流 https://blog.csdn.net/appledurian/article/details/73135343
2. RTP协议全解析(H264码流和PS流)https://blog.csdn.net/chen495810242/article/details/39207305


(重要)以下代码并未实测,除ts的发送外,其余都是伪代码(并且未搜集资料查询思路是否正确), 这边只为自己记录,参考请谨慎, 自己记录下而已。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <getopt.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h> static union { char c[]; unsigned long mylong; } endian_test = {{ 'l', '?', '?', 'b' } }; #define ENDIANNESS ((char)endian_test.mylong) #define PRINTF_DEBUG
#define TAB44 " " #define MAX_ARGS_FILEFORMAT 10
#define MAX_ARGS_FILEPATH 128
#define MAX_RTPURL_IP 128
#define MAX_ARGS_RTPURL 256
#define MTU 1400 #define DEFAULT_FILE_PATH "./videos/mux/ts_test.ts"
#define DEFAULT_FILE_FORMAT "ts"
#define DEFAULT_RTP_URL "rtp://127.0.0.1:8888" #define DEFAULT_ARGS {0, DEFAULT_FILE_PATH, DEFAULT_FILE_FORMAT, DEFAULT_RTP_URL} /* define4ts */
#define MAX_TS_PACKET_COUNT 7
#define TS_PACKET_LEN 188 /* define4ps */
#define SCODE_PS_END 0x000001B9
#define SCODE_PS_HEADER 0x000001BA
#define SCODE_PS_SYSTEM_HEADER 0x000001BB
#define SCODE_PS_SYSTEM_MAP_HEADER 0x000001BC /* define4mpeg2 */
typedef enum e_mpeg2_sc_type
{
E_SC_MPEG2_SEQ_HEADER = 0x000001B3,
E_SC_MPEG2_SEQ_PIC_EXTEN_HEADER = 0x000001B5,
E_SC_MPEG2_SEQ_END = 0x000001B7,
E_SC_MPEG2_GROUP_HEADER = 0x000001B8,
E_SC_MPEG2_PICTURE_HEADER = 0x00000100
} E_MPEG2_SC_TYPE; typedef enum e_rtp_playload_type
{
E_RTP_PLAYLOAD_TS = ,
E_RTP_PLAYLOAD_PS = ,
E_RTP_PLAYLOAD_MPEG4 = ,
E_RTP_PLAYLOAD_H264 = ,
} E_RTP_PLAYLOAD_TYPE; typedef struct t_args
{
unsigned short isLoop; unsigned char filePath[MAX_ARGS_FILEPATH+];
unsigned char fileFormat[MAX_ARGS_FILEFORMAT+];
unsigned char rtpUrl[MAX_ARGS_RTPURL+];
} T_ARGS; /******************************************************
个人理解
1. 位域内单字节的内存排布是定义的先后, 先定义的在内存的低地址;
2. 位域内单字节, 字节由高到低, 先定义的为高字节;
3. 因此对于小端(低地址放低字节).
******************************************************/
typedef struct t_rtp_header
{
#if 1 /* 小端, BIG_ENDIAN系统宏, 暂不知道怎么用 */
/* bytes 0 */
unsigned char csrc_len:;
unsigned char extension:;
unsigned char padding:;
unsigned char version:;
/* bytes 1*/
unsigned char playload:;
unsigned char marker:;
#else
/* bytes 0 */
unsigned char version:;
unsigned char padding:;
unsigned char extension:;
unsigned char csrc_len:;
/* bytes 1*/
unsigned char marker:;
unsigned char playload:;
#endif /* byte 2, 3 */
unsigned short seq_no;
/* bytess 4-7 */
unsigned int timestamp;
/* bytes 8-11 */
unsigned int ssrc;
} T_RTP_HEADER; /* gloabl data */
FILE *fp = NULL; struct sockaddr_in servAddr; T_ARGS defaultArgs = DEFAULT_ARGS; static void Usage(void)
{
fprintf(stderr, "usage: rtpserver [options]\n\n"
"Options:\n"
"-l | --stream_loop Read and send strame for loop\n"
"-i | --filepath File need to send\n"
"-f | --fileformat Container of file(support ts, ps, h264, mpeg2, flv)\n"
"-s | --rtpurl Rtp url include ip and port\n"
"-h | --help Print this message\n");
} /******************************************************************************
1. const char shortOpt[] = "li:f:s:h";
单个字符表示选项;
单个字符后接一个冒号, 表示后面必须跟一个参数. 参数紧跟选项后或者加一个空格;
单个字符后接两个冒号, 表示可有也可没有, 参数紧跟选项后, 不能加空格.
2. 参数的值赋给了optarg;
3. c = getopt_long(argc, argv, shortOpt, longOpt, NULL);
返回值为参数字符, 若全部解析完成则返回-1.
******************************************************************************/
static void ParseArgs(int argc, char *argv[], T_ARGS *args)
{
int c = ; const char shortOpt[] = "li:f:s:h";
const struct option longOpt[] = {
{"stream_loop", no_argument, NULL, 'l'},
{"filepath", required_argument, NULL, 'i'},
{"fileformat", required_argument, NULL, 'f'},
{"rtpurl", required_argument, NULL, 's'},
{"help", no_argument, NULL, 'h'},
{, , , }
}; for (;;)
{
c = getopt_long(argc, argv, shortOpt, longOpt, NULL); if (- == c)
{
break;
} switch (c)
{
case 'l':
args->isLoop = ; break; case 'i':
memcpy(args->filePath, optarg, strlen(optarg)); args->filePath[strlen(optarg)] = '\0'; break; case 'f':
if (( != strcmp(optarg, "ts"))
&& ( != strcmp(optarg, "ps")
&& ( != strcmp(optarg, "h264")
&& ( != strcmp(optarg, "mpeg2")
&& ( != strcmp(optarg, "flv"))
{
Usage(); exit();
} memcpy(args->fileFormat, optarg, strlen(optarg)); args->fileFormat[strlen(optarg)] = '\0'; break; case 's':
memcpy(args->rtpUrl, optarg, strlen(optarg)); args->rtpUrl[strlen(optarg)] = '\0'; break; default:
Usage(); exit();
}
}
} static void Parse_RtpUrl(unsigned char* const rtpUrl, unsigned char *urlIp, unsigned short *urlPort)
{
unsigned short port = ; unsigned char *url = NULL;
unsigned char *portStart = NULL; url = rtpUrl; url += strlen("rtp://"); portStart = strstr(url, ":"); port = atoi(portStart+); *urlPort = port; memcpy(urlIp, url, portStart-url);
} static unsigned long GetTickCount()
{
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (ts.tv_sec * + ts.tv_nsec / );
} static void Rtp_Header_Costruct(T_RTP_HEADER *rtpHeader, E_RTP_PLAYLOAD_TYPE playloadType)
{
static unsigned short seqNo = ; rtpHeader->version = ;
rtpHeader->padding = ;
rtpHeader->extension = ;
rtpHeader->csrc_len = ;
rtpHeader->marker = ;
rtpHeader->playload = playloadType;
rtpHeader->seq_no = seqNo++;
rtpHeader->timestamp = htonl(GetTickCount()*/);
rtpHeader->ssrc = htonl();
} static void Rtp_DealTs(int socketFd)
{
int readLen = ;
int bufCount = ;
int packetCount = ; unsigned char rtpBuf[MTU] = {}; memset(rtpBuf, 0x0, MTU); Rtp_Header_Costruct((T_RTP_HEADER*)rtpBuf, E_RTP_PLAYLOAD_TS); bufCount = sizeof(T_RTP_HEADER); while ()
{
if (feof(fp))
{
if (defaultArgs.isLoop)
{
rewind(fp); packetCount = ; memset(rtpBuf, 0x0, MTU); Rtp_Header_Costruct((T_RTP_HEADER*)rtpBuf, E_RTP_PLAYLOAD_TS); bufCount = sizeof(T_RTP_HEADER);
}
else
{
break;
}
} readLen = fread(rtpBuf+bufCount, , TS_PACKET_LEN, fp); packetCount++;
bufCount += readLen; if (packetCount>=MAX_TS_PACKET_COUNT)
{
sendto(socketFd, rtpBuf, bufCount, , (const struct sockaddr*)&servAddr, sizeof(servAddr)); packetCount = ; memset(rtpBuf, 0x0, MTU); Rtp_Header_Costruct((T_RTP_HEADER*)rtpBuf, E_RTP_PLAYLOAD_TS); bufCount = sizeof(T_RTP_HEADER); usleep(); // 应根据帧率发送或者可实现rtcp来动态控制发送速度
}
}
} static void Rtp_DealPs(int socketFd)
{
int readLen = ; unsigned int startCode = ; while ()
{
if ( != fread(&startCode, , , fp))
{
break;
} switch (startCode)
{
case SCODE_PS_END:
break; case SCODE_PS_HEADER:
/* get and send, like psparse.c */ break; case SCODE_PS_SYSTEM_HEADER:
/* get and send, like psparse.c */ break; case SCODE_PS_SYSTEM_MAP_HEADER:
/* get and send, like psparse.c */ break; default:
/*
1. get and send, like psparse.c;
2. here data mybe>MTU, 分包, 每次发MTU, 直到全部完成;
3. rtp头上的marker标识了一帧的开始/结束, 分包的时候刚开始写0, 最后一包填1;
4. 未证实: 分包时rtp头的timestamp应该是不变的.
*/ break;
}
}
} /**************************************************************************************************************************
1. 组合封包模式
在NALU单元很小的时候, 可以将多个NALU封装到一个RTP包里面进行传输, 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24;
那么这里的类型值分别是24, 25, 26以及27;
我们主要介绍STAP-A, 其封包格式如下所示:
[RTP Header] [Nalu头, type: 24(一个字节78)] [Nalu1 len(2 bytes)] [Nalu1 data] [Nalu2 len(2 bytes)] [Nalu2 data] ...
2. 分片封包模式
当一个NALU长度超过了MTU, 就需要采用分片的方式进行RTP封包, 将一个NALU分到多个RTP包中进行传输;
存在两种分片类型FU-A和FU-B, 类型值分别是28和29.
RTP+FU-A分片封包的组合方式如下:
[RTP Header][FU indicator][FU header][payload]: 其中RTP Header占12字节, FU indicator和FU header各占1个字节; [FU indicator]有以下格式:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
type=28表示FU-A分包 [FU header]的格式如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S: 设置成1表示此FU-A分片包为NAL单元的起始包, 其他情况设置为0;
E:设置成1表示此FU-A分片为NAL单元的结束包, 其他情况设置为0;
R:保留位,必须为0;
Type: 为被分包的Nalu的type. 简单说就是加了一个字节描述分包的开始和结束.
*******************************************************************************************************************************/
static void Rtp_DealH264(int socketFd)
{
while (!feof(fp))
{
/*
1. get nalu data;
2. sps, pps等较小的, 可采用组合封包;
3. 帧数据大于MTU, 需分包. 如FU-A, 将帧拆分, 加上FU-A的格式, 再加上FTP的头发送出去;
4. 以上都可参照h264parse.c
*/
}
} static void Rtp_DealMpeg2(int socketFd)
{
while (!feof(fp))
{
/*
1. get data by startcode(seq, gop, pic...);
2. data_len<MTU, send;
3. data_len>MTU, 分包, 每次最大MTU;
4. 以上都可参照mpeg2parse.c
*/
}
} static void Rtp_DealFlv(int socketFd)
{
while (!feof(fp))
{
/*
1. get data by tag(script, video, audio...);
2. data_len<MTU, send;
3. data_len>MTU, 分包, 每次最大MTU;
4. 以上都可参照flvparse.c
*/
}
} /*
1. rtp client, send data to servAddr;
2. server can play used rtp://ip:port
*/
int main(int argc, char *argv[])
{
int socketFd = ; unsigned short serverPort = ; unsigned char serverIp[MAX_RTPURL_IP] = {}; ParseArgs(argc, argv, &defaultArgs); memset(serverIp, 0x0, MAX_RTPURL_IP); Parse_RtpUrl(defaultArgs.rtpUrl, serverIp, &serverPort); socketFd = socket(AF_INET, SOCK_DGRAM, );
if (socketFd < )
{
printf("%s\n", strerror(errno)); exit();
} memset(&servAddr, , sizeof(servAddr)); servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(serverPort);
servAddr.sin_addr.s_addr = inet_addr(serverIp); fp = fopen(defaultArgs.filePath, "r+");
if (!fp)
{
printf("%s\n", strerror(errno)); exit();
} if ( == strcmp(defaultArgs.fileFormat, "ts"))
{ Rtp_DealTs(socketFd); fclose(fp);
}
else if ( == strcmp(defaultArgs.fileFormat "ps"))
{
Rtp_DealPs(socketFd); fclose(fp);
}
else if ( == strcmp(defaultArgs.fileFormat "h264"))
{
Rtp_DealH264(socketFd); fclose(fp);
}
else if ( == strcmp(defaultArgs.fileFormat "mpeg2"))
{
Rtp_DealMpeg2(socketFd); fclose(fp);
}
else if ( == strcmp(defaultArgs.fileFormat "flv"))
{
Rtp_DealFlv(socketFd); fclose(fp);
} return ;
}
最后如果您觉得本篇对您有帮助,可以打赏下,谢谢!!!

rtp传输音视频(纯c代码)的更多相关文章
- ffmpeg解码音视频过程(附代码)
0. 引言 最近一直在使用和学习ffmpeg. 工作中需要拉流解码, 获取音频和视频数据. 这些都是使用ffmpeg处理. 因为对ffmpeg接触不多, 用的不深, 在使用的过程中经常遇到不太懂的地方 ...
- Android IOS WebRTC 音视频开发总结(八十六)-- WebRTC中RTP/RTCP协议实现分析
本文主要介绍WebRTC中的RTP/RTCP协议,作者:weizhenwei ,文章最早发表在编风网,微信ID:befoio 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID ...
- Android IOS WebRTC 音视频开发总结(五七)-- 网络传输上的一种QoS方案
本文主要介绍一种QoS的解决方案,文章来自博客园RTC.Blacker,欢迎关注微信公众号blacker,更多详见www.rtc.help QoS出现的背景: 而当网络发生拥塞的时候,所有的数据流都有 ...
- 5┃音视频直播系统之 WebRTC 中的协议UDP、TCP、RTP、RTCP详解
一.UDP/TCP 如果让你自己开发一套实时互动直播系统,在选择网络传输协议时,你会选择使用UDP协议还是TCP协议 假如使用 TCP 会怎样呢?在极端网络情况下,TCP 为了传输的可靠性,将会进行反 ...
- 腾讯技术分享:微信小程序音视频与WebRTC互通的技术思路和实践
1.概述 本文来自腾讯视频云终端技术总监rexchang(常青)技术分享,内容分别介绍了微信小程序视音视频和WebRTC的技术特征.差异等,并针对两者的技术差异分享和总结了微信小程序视音视频和WebR ...
- C++实现RTMP协议发送H.264编码及AAC编码的音视频
http://www.cnblogs.com/haibindev/archive/2011/12/29/2305712.html C++实现RTMP协议发送H.264编码及AAC编码的音视频 RTMP ...
- C++实现RTMP协议发送H.264编码及AAC编码的音视频(转)
C++实现RTMP协议发送H.264编码及AAC编码的音视频(转) RTMP(Real Time Messaging Protocol)是专门用来传输音视频数据的流媒体协议,最初由Macromedia ...
- 【转】C++实现RTMP协议发送H.264编码及AAC编码的音视频
RTMP(Real Time Messaging Protocol)是专门用来传输音视频数据的流媒体协议,最初由Macromedia 公司创建,后来归Adobe公司所有,是一种私有协议,主要用来联系F ...
- 鹅厂优文|打通小程序音视频和webRTC
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 作者:腾讯视频云终端技术总监常青, 2008 年毕业加入腾讯,一直从事客户端研发相关工作,先后参与过 PC QQ.手机QQ.QQ物联 等产品 ...
随机推荐
- HTML与CSS的一些知识(二)
续: 5.表单标签<form></form> 用于收集用户信息,统一提交到服务器 一般用input标签收集,再用提交按钮提交:input标签根据type属性值不同有不同的类型: ...
- server.Transfer不工作
https://www.codeproject.com/Questions/56736/How-to-use-Server-Transfer-from-Ajax-UpdatePanel For Ser ...
- 关于win10环境下Anaconda python,用pip安装包及升级时SSL报错的问题
刚开始查完以为是网的问题,但是添加信任值\镜像网站\手动下载安装都失败了. 检查后发现python中无SSL模块,检查Anaconda后发现是少加了环境变量Anaconda3\Library\bin, ...
- Git初识学习
初始化一个Git仓库,使用git init命令. 添加文件到Git仓库,分两步: 使用命令git add <file>,注意,可反复多次使用,添加多个文件: 使用命令git commit ...
- 彻底搞懂Gradle、Gradle Wrapper与Android Plugin for Gradle的区别和联系
首先用一段通俗易懂但是不是非常专业的话描述一下三者的概念.区别和联系. Gradle是个构建系统,能够简化你的编译.打包.测试过程.熟悉Java的同学,可以把Gradle类比成Maven. Gradl ...
- Java中的集合类,集合类有哪些,如何增添删除元素,如何遍历
http://www.cnblogs.com/LittleHann/p/3690187.html import java.util.*; public class TestCollection { p ...
- Unity --- 设置选择的图片的格式
static void Setting(string assetPath, int maxSize, TextureImporterFormat androidFormat, TextureImpor ...
- Java程序设计的第一次作业1
- 深入理解Plasma(四)Plasma Cash
这一系列文章将围绕以太坊的二层扩容框架 Plasma,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等.本篇文章主要介绍在 Plasma 框架下的项目 Plasma Cash. 在上一篇 ...
- 你所误解的微信公众号开发、以及微信公众号网页授权、接收url跳转参数等问题
前言:有一星期没跟新博客了,最近太忙.项目赶进度就没把时间花在博客上:今天来说说所谓的微信公众号开发和填坑记录: 微信公众号:运行在微信终端的应用 (对于开发者来说比较爽的你只需考虑兼容微信浏览器,因 ...