【SZU计算机网络实验】实现码率自适应传输

前言

哈哈实验截止时间推迟了一周,不变的还是被ddl追着跑

本次实验主要在上次实验(流式视频传输)的基础上,模拟网络波动的情况,设计从客户端根据网络波动情况请求不同码率的视频片段的算法,从而提升用户体验(quality of experience, or QoE)

相关资料:

实验文档:计算机网络课程综合实验平台 (snrc.site)

一、引入网络波动

实验提供的文件库fluctuation.h 中包含了许多与引入网络波动相关的函数,将其在server.c中引入

c 复制代码
#include "fluctuation.h"

在代码初始化阶段调用 load_fl() 函数初始化网络波动相关信息

c 复制代码
    /***初始化阶段***/
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    int server_fd, new_socket;
    struct sockaddr_in server_address;
    int opt = 1;
    int addrlen = sizeof(server_address);
    char buffer[BUFFER_SIZE] = {0};
    
    // 初始化网络波动相关信息
    load_fl();

将server.c中所有的send更改为send_fl(),键盘点击ctrl+F

在结束阶段调用 release_fl() 函数释放为网络波动模拟模块所分配系统资源。

c 复制代码
    /***结束阶段***/
    // 释放为网络波动模拟模块所分配系统资源
    release_fl();
    
    closesocket(server_fd);
    closesocket(new_socket);
    WSACleanup();

到此网络波动模块就引入成功了

其实程序在运行时便会读取配置文件variation.cfg,实验文件中还提供了不同的网络波动,在文件BandwidthTrace.txt中,可选择其一将其复制到配置文件中。

BandwidthTrace.txt:

txt 复制代码
Trace1:
100,100,2000
200,200,2000
400,400,2000
800,800,2000
1600,1600,2000
3200,3200,2000
1600,1600,2000
800,800,2000
400,400,2000
200,200,2000
100,100,2000

Trace2:
10,1,1000
250,250,1000
500,500,1000
750,750,1000
1000,1000,1000
1250,1250,1000
1500,1500,1000
1750,1750,1000
2000,2000,1000
2500,2500,1000
3000,3000,1000
2500,2500,1000
2000,2000,1000
1750,1750,1000
1500,1500,1000
1250,1250,1000
1000,1000,1000
750,750,1000
500,500,1000
250,250,1000

Trace3:
10,1,15000
3500,3500,5000
700,700,10000
2000,2000,5000

variation.cfg:

txt 复制代码
10,1,15000
3500,3500,5000
700,700,10000
2000,2000,5000

可以看到默认的配置是BandwidthTrace.txt中的Trace3

将服务端和客户端都跑起来,发现网页播放的视频出现卡顿,说明网络波动引入成功

二、理解ABR算法

在客户端client.c 中引入实现ABR算法所需要的文件库abrlib.h

库中函数允许我们创建一个队列,将需要请求的视频片段的文件名做入队和出队操作

c 复制代码
/************1. 添加abr相关参数的头文件******************************/
#include "abrlib.h"
/*******************************************************************/

在初始化阶段添加下载队列相关初始代码

c 复制代码
    /***初始化阶段***/
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    /************2. 在初始化阶段添加下载队列相关初始代码*******************/
	Queue download_queue;
	QueueInit(&download_queue);
	
	//在下载队列中,初始化下载chunk0和chunk1
	QueuePush(&download_queue,"ocean-1080p-8000k-0.ts");
	QueuePush(&download_queue,"ocean-1080p-8000k-1.ts");
	
	int video_id=0; //当前下载视频的chunk id
	int current_bitrate=0;  //当前下载的码率 (kbps)
	int last_bitrate=0;  //上一轮下载视频的码率 (kbps)
	/*******************************************************************/

我们初始请求两个码率最高的视频片段,之后根据第一个的视频片段的下载时间决定请求第三个视频的码率,根据第二个视频片段的下载时间决定请求第四个视频的码率,以此类推,即:

1 -> 3 -> 5 -> ...

2 -> 4 -> 6 -> ...

默认的ABR算法如下:

c 复制代码
		/*****5. 在下载结束后,统计QoE,并为之后的下载进行简单的ABR自适应决策**/

		double rebuffering_time=ReceiveSegment(video_segement, req, file_size);   //获取播放器卡顿时间ms
		double buffer_occupancy=GetBufferSize();    //获取播放器缓冲区长度ms
		
		printf("%s tran time: %ld\n", req, download_time);
		
		//统计用户体验质量
		QoERecord(current_bitrate,last_bitrate,rebuffering_time);
		putchar('\n');
		
		last_bitrate=current_bitrate;	
		
		//简单的码率自适应决策
		int next_id=video_id+2; //间隔一个chunk进行下载
		if(next_id<=VIDEO_LEN-1){
			/*****Without ABR******/
			
//		char temp[REQUEST_SIZE]="ocean-1080p-8000k";
//	        char suffix[6]="";
//	        sprintf(suffix, "-%d.ts", next_id);  
//	        strcat( temp, suffix);
//	        QueuePush(&download_queue,temp);    //将决策结果入队

			/**********************/
			
			/********With ABR**********/
		    if(download_time<200){
		        char temp[REQUEST_SIZE]="ocean-1080p-8000k";
		        char suffix[6]="";
		        sprintf(suffix, "-%d.ts", next_id);  
		        strcat( temp, suffix);
		        QueuePush(&download_queue,temp);    //将决策结果入队
		    }else if(download_time<600){
		        char temp[REQUEST_SIZE]="ocean-480p-2500k";
		        char suffix[6]="";
		        sprintf(suffix, "-%d.ts", next_id);  
		        strcat( temp, suffix);
		        QueuePush(&download_queue,temp);    //将决策结果入队
		    }else{
		        char temp[REQUEST_SIZE]="ocean-360p-1000k";
		        char suffix[6]="";
		        sprintf(suffix, "-%d.ts", next_id);  
		        strcat( temp, suffix);
		        QueuePush(&download_queue,temp);    //将决策结果入队
		    }
		    /***************************/
		}	
		
           //释放内存
		free(video_segement);   
		video_segement=NULL;
		video_id++; //更新下载id
		QueuePop(&download_queue);
		/*******************************************************************/

记住在sample code中,释放内存部分代码没有出队操作,需要自行编写

可以看到,默认的ABR算法仅仅从下载时间的角度去实现

运行并输出QoE的累加和

c 复制代码
	QoECount();
	putchar('\n');

发现默认的ABR算法能一般都能达到80+甚至100+

三、设计ABR算法

实验文档中提示我们能从几个角度去设计ABR算法:

  1. 基于带宽预测
  2. 基于缓冲区的容量
  3. 基于神经网络

由上述内容我们可以发现,默认的ABR算法只利用了下载时间(download_time) ,其实还有两个变量:缓冲区长度(buffer_occupancy)卡顿时间(rebuffering_time) 没有利用到

我们不妨定义一个评价标准,使其同时关联到这三个变量:

<math xmlns="http://www.w3.org/1998/Math/MathML"> s t a n d a r d = θ 1 b u f f e r O c c u p a n c y − θ 2 d o w n l o a d T i m e − θ 3 r e b u f f e r i n g T i m e standard = \theta_1 bufferOccupancy - \theta_2 downloadTime - \theta_3 rebufferingTime </math>standard=θ1bufferOccupancy−θ2downloadTime−θ3rebufferingTime

为了方便讨论,我们先取 <math xmlns="http://www.w3.org/1998/Math/MathML"> θ 1 = θ 2 = θ 3 = 1 \theta_1 = \theta_2 = \theta_3 = 1 </math>θ1=θ2=θ3=1,即

<math xmlns="http://www.w3.org/1998/Math/MathML"> s t a n d a r d = b u f f e r O c c u p a n c y − d o w n l o a d T i m e − r e b u f f e r i n g T i m e standard = bufferOccupancy - downloadTime - rebufferingTime </math>standard=bufferOccupancy−downloadTime−rebufferingTime

c 复制代码
            double standard = buffer_occupancy - (double)download_time - rebuffering_time;
            printf("%s\n", req);
            printf("buffer occupancy: %.6f, download time: %.6f, rebufffering time: %.6f\n",
                            buffer_occupancy, (double)download_time, rebuffering_time);
            printf("standard: %.6f\n", standard);

之后便是仿照默认ABR,根据标准值所在的区间,这里取边界值为**-2000 和 2000**,决定请求视频片段的码率:

c 复制代码
            char temp[REQUEST_SIZE] = "";
            if(standard > 2000){
                    strcat(temp, "ocean-1080p-8000k");
            }else if(standard > -2000){
                    strcat(temp, "ocean-480p-2500k");
            }else{
                    strcat(temp, "ocean-360p-1000k");	
            }
            char suffix[6]="";
            sprintf(suffix, "-%d.ts", next_id);  
            strcat( temp, suffix);
            QueuePush(&download_queue,temp);    //将决策结果入队

测试发现这种方法能稳定120+,某些情况下稳定300+,比默认方法要高出几倍

想要继续优化算法的性能可以接着讨论standard的三个参数以及分割区间的两个边界值总共五个变量的值

四、思考题

思考题1

如果有某一用户群体,相较于其他人,对视频质量的波动没有那么在意,而较低的视频质量会使他们更加恼火。针对该用户群体,当前QoE评分方式是否合适?如果不合适,如何调整?

实验文件在abrlib.h中定义的QoE如下:

c 复制代码
// 视频质量相关参数
double QoE_count = 0; // 用户体验质量统计 (Quality of experience)
double alpha = 0.001;
double beta = 0.0005;
double gama = 0.01;

// 记录当前下载的QoE
void QoERecord(int current_bitrate, int last_bitrate, double rebuffering_time)
{	
	double QoE = alpha * current_bitrate - beta * abs(last_bitrate - current_bitrate) - gama * rebuffering_time;
	QoE_count += QoE;
}

当用户对视频质量更加在意时,应该提高alpha的值;对视频质量的波动没那么在意,则应该适当降低beta的值

思考题2

用户在观看常规视频时可以完整观看到视频的全部内容,而在沉浸式视频(360°视频、点云视频等)中,用户会选择性的观看部分视频内容。对于沉浸式视频,如何评估用户的视频观看体验?

在360°视频中,用户只能看到部分的视频内容,所以只需要以这部分视频的质量(包括码率,卡顿时间,缓冲区时间等)来衡量用户的观看体验,而不是整个视频的质量。

360°视频在传输的过程中,可以将视频片段切分成许多tiles,根据用户的头部运动轨迹,预测用户下一步可能需要观看的部分视频,选择性地传输不同码率的这些视频

因此,还可以从由于切分tiles导致的视频片段空间上的码率波动 ,由于预测不准确性导致的延迟等角度评估用户的观看体验

相关推荐
我命由我123451 分钟前
STM32 开发 - 中断案例(中断概述、STM32 的中断、NVIC 嵌套向量中断控制器、外部中断配置寄存器组、EXTI 外部中断控制器、实例实操)
c语言·开发语言·c++·stm32·单片机·嵌入式硬件·嵌入式
游戏开发爱好者813 分钟前
iOS App上线前的安全防线:项目后期如何用Ipa Guard与其他工具完成高效混淆部署
websocket·网络协议·tcp/ip·http·网络安全·https·udp
Amy.Wang16 分钟前
常见的网络协议有哪些
网络·网络协议
宋一平工作室42 分钟前
单片机队列功能模块的实战和应用
c语言·开发语言·stm32·单片机·嵌入式硬件
SY师弟1 小时前
台湾TEMI协会竞赛——2、足球机器人组装教学
c语言·单片机·嵌入式硬件·机器人·嵌入式·台湾temi协会
whoarethenext1 小时前
使用 C/C++的OpenCV 将多张图片合成为视频
c语言·c++·opencv
心月狐的流火号1 小时前
Java网络编程深度解析:TCP与UDP如何共享同一端口
网络协议
只与明月听1 小时前
前端学算法-二叉树(一)
前端·javascript·算法
freyazzr1 小时前
TCP/IP 网络编程 | Reactor事件处理模式
开发语言·网络·c++·网络协议·tcp/ip
电院工程师1 小时前
SM3算法Python实现(无第三方库)
开发语言·python·算法·安全·密码学