实现特定SDP报文转化为C结构
一、根据SDP规范定义C结构体
下面是一个sdp报文:
1v=0
2o=HWPSS 3427743244 1084119141 IN IP4 127.0.0.1
3s=test1.mp4 // test1.mp4:媒体文件名
4c=IN IP4 0.0.0.0
5t=0 0
6a=control:*
7a=range:npt=0-44.000000
8m=video 0 RTP/AVP 96 // 96:track->payload_type 视频:96音频:97
9a=control:trackID=101 // 101:轨道ID。 视频:101,102, 103 音频: 201,202, 203,204, 205
10a=rtpmap:96 MP4V-ES/90000
11a=fmtp:96 profile-level-id=2;config=000001b0020;
12m=audio 0 RTP/AVP 97
13a=control:trackID=201
14a=rtpmap:97 mpeg4-generic/24000/1
15a=fmtp:97 streamtype=5;profile-level-id=15; mode=AAC-hbr; config=1308; SizeLength=13; IndexLength=3;IndexDeltaLength=3; Profile=1;
需要注意的地方:
- 媒体数目, 媒体内的a字段数目都是不固定的。如果用C语言结构体装的话,可能需要内存分配和指针等东西。(目前想法是可以通过柔性数组处理实现)
- 单个字段内的数据组成也是不定的,可以先暂时取出文本域,再将文本域通过空格分为数组,至于之后的具体是什么内容,可以另写函数处理。
目前暂时就定义这个报文里面有的东西。
二、文件流模拟输入
读文件demo
feof函数判断文件结尾,fgets读取一行,遇到换行停止(会读取换行符)。
1#include <stdio.h>
2
3int main(){
4 FILE *file = NULL;
5 char buff[255];
6
7 file = fopen("sdp.txt", "r");
8 if(file == NULL){
9 printf("Error opening file");
10 return 1;
11 }
12
13 while(!feof(file)){
14 fgets(buff, 255, (FILE*)file);
15 printf("%s", buff);
16 }
17
18
19 fclose(file);
20 return 0;
21}
22
三、字符处理转化结构体
核心思想:
- 先初始分配一个默认的内存, 存放1个媒体, 且a行只有1个。
- 先扫描一遍文本,记录会话a行, 媒体个数, 各个媒体的a行个数。
- 如果在上面的过程中遇到了多个a,多个m,使用字段记录变动的字段,再立刻使用realloc进行动态拓展。
遇到的问题:
- 多维数组指针索引遍历问题, 这个去翻C Primer Plus的302页即可。(已解决)
- 结构体中的柔性数组内存分配。(以解决)
- 字符打印始终是乱码,乱码出现在柔性数组那里,会话部分有些乱码有些没有,且每次乱码的位置都是固定的。但是后面的柔性数组没有乱码。
- 好像malloc的内存自动会释放,如果自己再调用free的话会报
free(): double free detected in tcache 2(已解决) - 不额外写printf的话会报malloc(): corrupted top size的错误, 非常奇怪。
头文件sdp_handle.h:
1#ifndef SDP_HANDLE_H
2#define SDP_HANDLE_H
3#define STR_LENGTH 64
4#define DEFAULT_MEDIA_ATTRIBUTE 2
5#define DEFAULT_SESSION_ATTRIBUTE 2
6
7typedef struct _media {
8 char information[STR_LENGTH]; //m
9 int a_count; //记录一个媒体的a行个数
10 char attribute[][STR_LENGTH]; //a
11} Media;
12
13typedef struct _session {
14 char version; //v
15 char originator[STR_LENGTH]; //o
16 char name[STR_LENGTH]; //s
17 char connection_information[STR_LENGTH]; //c
18 int time_active[2]; //t
19 int media_count; //记录一次会话的媒体个数
20 Media* media[5]; //m,由于变长数组只能有一个,暂时写死
21 int a_count; //记录会话的a属性个数
22 char attribute[][STR_LENGTH]; //a
23} Session;
24
25
26//字符串处理为结构体
27void handle_line_to_struct(Session* session, FILE* file);
28//打印会话信息到控制台
29void print_session(Session session);
30//将会话信息持久化到文件
31void save_session(Session session, char* file_name);
32//创建会话内存
33Session* malloc_session(Session* session);
34//清理会话内存
35void destory_session(Session* session);
36//创建媒体内存
37Media* malloc_media(Media* media);
38//清理媒体内存
39void destory_media(Media* media);
40//字符串指针复制函数
41void strcpy_p(char* from, char* dest, char stopsign);
42
43
44#endif
函数文件sdp_handle.c:
1#include <stdio.h>
2#include <string.h>
3#include <stdlib.h>
4#include <stdbool.h>
5#include "sdp_handle.h"
6
7
8//----------------------处理字符函数----------------
9//处理字符串为结构体
10void handle_line_to_struct(Session* session, FILE* file){
11 char buff[255]; //缓冲区
12 int handled_a = 0; //记录已经处理的会话a行数
13 int handled_a_m = 0; //记录单个媒体已经处理的a行数
14
15 //1.读取文件
16 //TODO 定义安全检查函数
17 //TODO 错误处理
18 while(true){
19 fgets(buff, 255, (FILE*)file);
20 char* str = buff;
21
22 //报文末尾判断
23 if(feof(file)){
24 break;
25 }
26
27 //2.对每行进行parse
28 //char* seperate = strchr(str, '='); //显式地区分类别和文本
29 char type = *str; //字段类别头
30 char* begin = str+2; //文本的开始
31 //printf("%c\n", type);
32 switch(type){
33 //TODO 覆盖一般SDP报文的内容
34 case 'v':
35 //暂时假设v字段只有1位
36 session->version = *begin;
37 break;
38 case 'o':
39 strcpy_p(begin, session->originator, '\n');
40 break;
41 case 's':
42 strcpy_p(begin, session->name, '\n');
43 break;
44 case 'c':
45 strcpy_p(begin, session->connection_information, '\n');
46 break;
47 case 't':
48 //由于时间有可能是长位的,采用atoi函数
49 char* devide = strchr(begin, ' ');
50 devide++;
51 char start_time[20];
52 char stop_time[20];
53 strcpy_p(begin, start_time, ' ');
54 strcpy_p(devide, stop_time, '\n');
55 session->time_active[0] = atoi(start_time);
56 session->time_active[1] = atoi(stop_time);
57 break;
58 case 'm':
59 //创建媒体
60 Media* media;
61 media = malloc_media(NULL);
62 handled_a_m = 0; //计数器清零
63 strcpy_p(begin, media->information, '\n');
64 *(session->media + session->media_count)= media; //赋值给对应的媒体段
65 session->media_count++;
66 media = NULL;
67 break;
68 case 'a':
69 //判断a行的归属
70 if(session->media_count == 0){
71 //1.处理session的a
72 //如果a段的内存不够,进行扩容
73 if(handled_a >= session->a_count){
74 session->a_count += 1; //一次扩容1行
75 session = malloc_session(session);
76 }
77 //对应位置进行复制字符串
78 strcpy_p(begin, *(session->attribute+handled_a), '\n');
79 handled_a++;
80 }else{
81 //2.处理media的a
82 //还是先检查内存是否足够
83 Media* media_now;
84 media_now = *(session->media+(session->media_count-1));
85 if(handled_a_m >= media_now->a_count){
86 media_now->a_count += 1; //一次扩容1行
87 media_now = malloc_media(media_now);
88 }
89 //对应位置进行复制
90 strcpy_p(begin, *(media_now->attribute+handled_a_m), '\n');
91 handled_a_m++;
92 }
93 break;
94 default:
95 printf("Unkown Type:%c\n", type);
96 }
97 }
98
99}
100
101//复制字符串函数
102void strcpy_p(char* from, char* dest, char stopsign){
103 //复制
104 for(; *from != stopsign; from++, dest++){
105 *dest = *from;
106 }
107 //最后面加'\0'
108 *dest = '\0';
109}
110
111
112//-----------------输出函数-----------------
113
114//打印会话信息到控制台
115void print_session(Session session){
116 //printf("\n");
117 printf("Session Information:\n");
118 printf("------------------------------\n");
119 printf("version:%c\n", session.version);
120 printf("originator:%s\n", session.originator);
121 printf("name:%s\n", session.name);
122 printf("connection information:%s\n", session.connection_information);
123 printf("time_active, start:%d, end:%d\n", session.time_active[0], session.time_active[1]);
124 printf("attribute:\n");
125 for(int i = 0; i < session.a_count; i++){
126 printf("%s\n", (*(session.attribute+i)));
127 }
128 printf("\n");
129 printf("\n");
130
131 if(session.media_count == 0){
132 printf("No Media Information!\n");
133 }else{
134 printf("Media Information:\n");
135 printf("------------------------------\n");
136 for(int i = 0; i < session.media_count; i++){
137 Media* media_now = *(session.media+i);
138 printf("Media %d:\n", (i+1));
139 printf("information:%s\n", media_now->information);
140 printf("attribute:\n");
141 for(int j = 0; j < media_now->a_count; j++){
142 printf("%s\n", *(media_now->attribute + j));
143 }
144 printf("\n");
145 }
146 }
147}
148
149
150
151//会话持久化到rst文件
152void save_session(Session session, char* file_name){
153 //1.创建文件
154 FILE* write_file = fopen(file_name, "w+");
155
156 //2.写入文件
157 fprintf(write_file, "Session Information:\n");
158 fprintf(write_file, "------------------------------\n");
159
160 fprintf(write_file, "#. ");
161 fprintf(write_file, "version: ");
162 fputc(session.version, write_file);
163 fprintf(write_file, "\n\n");
164
165 fprintf(write_file, "#. ");
166 fprintf(write_file, "originator: ");
167 fprintf(write_file, "%s\n\n", session.originator);
168
169
170 fprintf(write_file, "#. ");
171 fprintf(write_file, "name: ");
172 fprintf(write_file, "%s\n\n", session.name);
173
174 fprintf(write_file, "#. ");
175 fprintf(write_file, "connection information: ");
176 fprintf(write_file, "%s\n\n", session.connection_information);
177
178 fprintf(write_file, "#. ");
179 fprintf(write_file, "time_active, start: ");
180 fprintf(write_file, "%d", session.time_active[0]);
181 fprintf(write_file, ", end: ");
182 fprintf(write_file, "%d\n\n", session.time_active[1]);
183
184 fprintf(write_file, "#. ");
185 fprintf(write_file, "attribute: \n");
186 for(int i = 0; i < session.a_count; i++){
187 fprintf(write_file, " - %s\n", (*(session.attribute+i)));
188 }
189 fprintf(write_file, "\n\n");
190
191 if(session.media_count == 0){
192 fprintf(write_file, "No Media Information!\n\n");
193 }else{
194 fprintf(write_file, "Media Information:\n");
195 fprintf(write_file, "------------------------------\n");
196 for(int i = 0; i < session.media_count; i++){
197 Media* media_now = *(session.media+i);
198
199 fprintf(write_file, "Media %d:\n\n", (i+1));
200
201 fprintf(write_file, "information:");
202 fprintf(write_file, "%s\n\n", media_now->information);
203
204 fprintf(write_file, "attribute:\n\n");
205 for(int j = 0; j < media_now->a_count; j++){
206 fprintf(write_file, "#. ");
207 fprintf(write_file, "%s\n\n", *(media_now->attribute + j));
208 }
209 }
210 }
211
212 //3.关闭文件
213 fclose(write_file);
214
215}
216
217
218
219
220//----------------处理内存-------------------
221
222//分配会话内存
223Session* malloc_session(Session* session){
224 Session* session_new = NULL;
225 if(session == NULL){
226 //初始化
227 printf("Initial session memory:%ld\n", sizeof(Session) + DEFAULT_SESSION_ATTRIBUTE * STR_LENGTH * sizeof(char));
228 session_new = (Session*)malloc(sizeof(Session) + DEFAULT_SESSION_ATTRIBUTE * STR_LENGTH * sizeof(char));
229 session_new->a_count=DEFAULT_SESSION_ATTRIBUTE;
230 session_new->media_count = 0;
231 }else{
232 //根据a_count动态分配内存
233 printf("Realloc session memory:%ld\n", sizeof(Session) + session->a_count * STR_LENGTH * sizeof(char));
234 session_new = (Session*)realloc(session, sizeof(Session) + session->a_count * STR_LENGTH * sizeof(char));
235 }
236
237 //判断内存是否分配成功
238 if(session_new == NULL){
239 printf("No aviliable memory for session!\n");
240 }
241
242 return session_new;
243}
244
245//清理会话内存
246void destory_session(Session* session){
247 printf("Clear session memory\n");
248 for(int i = 0; i < session->media_count; i++){
249 destory_media(*(session->media+i));
250 }
251 free(session);
252 session = NULL;
253}
254
255//分配媒体内存
256Media* malloc_media(Media* media){
257 Media* media_new = NULL;
258 //初始化判断
259 if(media == NULL){
260 //新分配内存
261 printf("Malloc new media memory\n");
262 media_new = (Media*)malloc(sizeof(Media) + DEFAULT_MEDIA_ATTRIBUTE*sizeof(char)*STR_LENGTH);
263 media_new->a_count = DEFAULT_MEDIA_ATTRIBUTE;
264 }else{
265 //动态根据参数a_count分配内存
266 printf("Realloc media memory\n");
267 media_new = realloc(media, sizeof(Media)+media->a_count*sizeof(char)*STR_LENGTH);
268 }
269
270 //内存分配失败检验
271 if(media_new == NULL){
272 printf("No memory aviliable for media\n");
273 }
274
275 return media_new;
276}
277
278//清理媒体内存
279void destory_media(Media* media){
280 printf("Clear media memory\n");
281 free(media);
282 media == NULL;
283}
284
四、文件流模拟输出&格式化打印结构体
对应函数在上面那节已经给出。
main.c文件:
1#include <stdio.h>
2#include <string.h>
3#include "sdp_handle.h"
4
5//TODO 修复不打印额外字符会出现:malloc(): corrupted top size的bug
6//命令行参数1作读取的sdp报文文件
7int main(int argc, char *argv[]){
8 //文件指针
9 FILE *file = NULL;
10 //会话结构体
11 Session* session = NULL;
12
13 //合法性检验
14 file = fopen("sdp.txt", "r");
15 if(file == NULL){
16 printf("Error opening file");
17 return 1;
18 }
19 //printf("Open file success\n");
20
21 //创建新会话
22 session = malloc_session(session);
23
24 //解析sdp.txt文件
25 handle_line_to_struct(session, file);
26 //关闭文件
27 fclose(file);
28
29 //打印结构体到控制台
30 print_session(*session);
31
32 //保存结构体为rst文件
33 save_session(*session, argv[1]);
34
35 //回收内存
36 destory_session(session);
37
38 return 0;
39}
Makefile:
1out: main.o sdp_handle.o
2 gcc -o out main.o sdp_handle.o
3
4sdp_handle.o:sdp_handle.c
5 gcc -c sdp_handle.c
6
7main.o: main.c
8 gcc -c main.c
9
10
11clean:
12 rm main.o sdp_handle.o out
13
14run:
15 ./out sdp.rst
命令行输出测试:
1./out sdp.rst
2Initial session memory:384
3Malloc new media memory
4Malloc new media memory
5Realloc media memory
6Session Information:
7------------------------------
8version:0
9originator:HWPSS 3427743244 1084119141 IN IP4 127.0.0.1
10name:test1.mp4
11connection information:IN IP4 0.0.0.0
12time_active, start:0, end:0
13attribute:
14cont��{�
15*p
16
17
18Media Information:
19------------------------------
20Media 1:
21information:video 0 RTP/AVP 96
22attribute:
23control:trackID=101
24fmtp:96 profile-level-id=2;config=000001b0020;
25
26Media 2:
27information:audio 0 RTP/AVP 97
28attribute:
29control:trackID=201
30rtpmap:97 mpeg4-generic/24000/1
31fmtp:97 streamtype=5;profile-level-id=15; mode=AAC-hbr; config=1308; SizeLength=13; IndexLength=3;IndexDeltaLength=3; Profile=1;
32
33Clear session memory
34Clear media memory
35Clear media memory
写文件输出测试:
1Session Information:
2------------------------------
3#. version: 0
4
5#. originator: HWPSS 3427743244 1084119141 IN IP4 127.0.0.1
6
7#. name: test1.mp4
8
9#. connection information: IN IP4 0.0.0.0
10
11#. time_active, start: 0, end: 0
12
13#. attribute:
14 - contx�s�
15 - �
16
17
18Media Information:
19------------------------------
20Media 1:
21
22information:video 0 RTP/AVP 96
23
24attribute:
25
26#. control:trackID=101
27
28#. fmtp:96 profile-level-id=2;config=000001b0020;
29
30Media 2:
31
32information:audio 0 RTP/AVP 97
33
34attribute:
35
36#. control:trackID=201
37
38#. rtpmap:97 mpeg4-generic/24000/1
39
40#. fmtp:97 streamtype=5;profile-level-id=15; mode=AAC-hbr; config=1308; SizeLength=13; IndexLength=3;IndexDeltaLength=3; Profile=1;
可见还是存在些许问题的,有些字段始终是乱码的, 但是找不到原因。
实现解析任意SDP报文
尚未解决问题:
- 各结构体需定义完善,覆盖所有可能的字段
- session段规定的一些字段是media段的默认值
- 有些字段如果media段规定,session段不需要规定
- optional字段需要注意
- 文本域里面的文本解析分离
- 需要判断报文的可用性,判断格式是否符合规范
暂时不考虑。
参考
下面是做这次小软件参考的链接:
如何使用fgets防止缓冲区溢出?_如何防止fgets在缓冲区溢出时多次运行?_防止缓冲区溢出 - 腾讯云开发者社区 - 腾讯云
vim 配置c/c++开发环境_vim配置c++开发环境 deepin-CSDN博客
(84 封私信 / 10 条消息) 如何在 Linux 下利用 Vim 搭建 C/C++ 开发环境? - 知乎
结构体嵌套问题的常见错误_c语言 结构体里嵌套结构体数组-CSDN博客
【转载】在linux下使用gcc/g++编译多个.h .c 文件_arm-linux-gnueabi-gcc编译多个。c 。h文件-CSDN博客
C语言动态内存分配_使用realloc重新分配内存后,务必要将原有地址使用free释放,否则会出现内存泄-CSDN博客
realloc(): invalid next size: C语言报错-CSDN博客
C> fgets读取文件最后一行重复问题 - 明明1109 - 博客园
C语言打印输出字符串的几种方法_c语言输出字符串-CSDN博客