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物联 等产品 ...
随机推荐
- redux&&createStore
const createStore = (reducer,presetState, enhancer) => { if (typeof presetState === "functio ...
- 2019-4-23 plan
需要制作springcloud es6的技术文档和demo
- Ubuntu如何启用root用户登录
默认安装Ubuntu都是不允许以root用户进行登录的,想要以root用户进行登录需要进行一些操作,主要是以下几个步骤: 第一步 在终端输入命令:sudo passwd root 以普通用户登录系统, ...
- Confluence-6.10.0+Jira-7.13+Crowd-3.2.1最全破解文档,附下载包
=========================================2019.4.19更改================================================ ...
- 【Core】当前 .NET SDK 不支持将 .NET Core 2.2 设置为目标。请将 .NET Core 2.1 或更低版本设置
问题起因: 新的电脑,打开core2.2的项目时,因为没有安装2.2 sdk,项目编译失败 所以在选择目标框架下拉框选择安装其他目标框架 会跳转到官网下载sdk:https://dotnet.micr ...
- react简书笔记一 环境, git 和 项目 关联
1.. 建立git项目 ( 码云, github 都可以 ), 具体步骤: https://www.cnblogs.com/andy-lehhaxm/p/10720717.html 1.1 git ...
- Go语言学习之13 日志管理平台开发
主要内容: 1. ElasticSearch介绍与使用2. kibana介绍与使用 1. ElasticSearch安装 详见上节内容2. kibana安装 (1) 下载ES,下载地址:https:/ ...
- Facebook主页照片和封面照片的尺寸要求
为什么好好的照片上传到Facebook后效果总不理想?为了避免你的照片在上传时被压缩,建议你尽量调整一下图片大小和格式,下面一起来看看Facebook主页照片和封面照片的尺寸要求. 1. Facebo ...
- 异常:Error resolving template "xxx", template might not exist or might not be accessible...解决办法
在开发环境下正常,但使用jar运行时,报错Error resolving template template might not exist or might not be accessible,意思 ...
- js vue 在页面中将摄像头放在一个标签里展示,(模仿手机拍照功能)
1.HTML <video id="video" autoplay class="fileImg"></video> <canva ...