H264码流插入和读取自定义数据(SEI字段)

news2024/12/23 17:20:20

目录

  • 1. 前言
  • 2. H264码流结构
    • 2.1 文字说明
    • 2.2 码流格式图解
  • 3. 自定义数据封装以及H264码流插入
    • 3.1 将自定义数据封装成SEI字段通用格式即可插入H264码流中
    • 3.2 编码逻辑
  • 4. 代码
  • 5. 总结

1. 前言

本文主要记录,如何在已有的H264码流中插入自定义的数据,并且不破坏H264码流结构,改造后的H264码流可以通过解码器正常解析出来,插入的自定义数据对H264码流解析不造成任何影响。
代码在文末附上,基于C/C++实现。

2. H264码流结构

2.1 文字说明

(1)H264码流,是由一个个独立的数据块NALU组成,NALU数据块之间相互关联,NALU数据块的顺序不可以调换,但NALU数据块之间可以插入指定格式的数据,理论上插入数据的长度不限。
(2)解码器在解码时,首先遍历码流中的Start code(00 00 00 01),找到Start code后紧跟着解析NALU单元,解析时从前往后遍历,直至找到下一个Start code,并开始下一帧NALU单元的解析。
(3)H264码流中插入自定义数据,首先将自定义数据按特定的格式封装成一个数组,然后在码流的每个I帧或者P帧之前插入自定义数据即可

2.2 码流格式图解

在这里插入图片描述
图2.1 H264码流基础数据块
在这里插入图片描述
图2.2 H264码流数据块组合示例

3. 自定义数据封装以及H264码流插入

3.1 将自定义数据封装成SEI字段通用格式即可插入H264码流中

在这里插入图片描述
图3.1 自定义数据封装格式

SEI字段自定义封装包格式为:

Start code:每个NALU单元的起始码
NRI:高四位的第一位为禁止位0,决定该NALU单元是否有效;高四位中间两位表示该NALU单元的重要程度,解码时根据该数值决定该NALU单元解析或丢弃。高四位的最后一位与低四位组成NALU单元识别码,06表示该单元为SEI字段。
payload type:05表示该SEI字段的编码格式符合H264标准格式
UUID:用户自定义的识别码,16字节长度,只要不与Start code冲突即可
自定义数据长度:用两个字节表示封装自定义数据的长度,解码时依据该数值做校验
自定义数据:用户自己封装的数据,类型必须为unsigned char
结尾对齐码:80表示SEI字段的结束对齐

3.2 编码逻辑

对H264码流进行for循环遍历,依据Start code 以及NALU头识别NALU单元类型,当识别到I帧或P帧时,将封装好的SEI数据包直接插入

4. 代码

h264Process.h

#include <stdio.h>
#include <string.h>
// uuid: 54 80 83 97 f0 23 47 4b b7 f7 4f 32 b5 4e 06 ac
// 定义SEI格式固定识别码
namespace SEI{
    static unsigned char start_code[] = {0x00,0x00,0x00,0x01};  //起始码
    static unsigned char sei_uuid[] = { 0x54, 0x8f, 0x83, 0x97, 0xf3, 0x23, 0x97, 0x4b, 
                                        0xb7, 0xc7, 0x4f, 0x3a, 0xb5, 0x6e, 0x89, 0x52 };   //自定义标识码
    static int sei_uuid_size = sizeof(sei_uuid);    //自定义标识码长度
    static unsigned char nal_type = 0x06;   //NAL类型,0x06标识该NAL单元为SEI
    static unsigned char payload_type = 0x05;   //0x05标识当前SEI编码格式为标准格式
    static unsigned char sei_tail = 0x80;   //SEI结尾对齐码
}
// 定位当前位置是否为SEI起始位置,返回值为SEI字段长度
int FindSei(unsigned char* h264_buf, int cur_index,int buf_size);

// 定位当前位置是否为I帧起始位置,若当前字节为I帧开始,返回1,否则返回0
int IFramelocat(unsigned char* h264_buf, int cur_index,int buf_size);

// 定位当前位置是否为P帧起始位置,若当前字节为P帧开始,返回1,否则返回0
int PFramelocat(unsigned char* h264_buf, int cur_index,int buf_size);

// 读取H264码流buffer,删除码流中原始SEI字段,并插入用户自定义数据,写入tmp文件中
int H264DelSeiInsertSeiBeforeFrame(unsigned char* h264_buf,int buf_size,unsigned char* sei_buf,int sei_buf_size,FILE *tmp);

// 将用户自定义数据user_data按照SEI格式封装进sei_buffer里
int FillSeiPacket(unsigned char* sei_buffer, int sei_size, unsigned char* user_data, int user_data_size);

// 从H264码流中解析SEI封装包,并从SEI封装包中解析用户数据
int H264GetSeiUserdata(unsigned char* h264_buf, int h264_buf_size, unsigned char* user_buf, int user_buf_size);


h264Process.cpp

#include "h264Process.h"

int FindSei(unsigned char* h264_buf, int cur_index,int buf_size){
    int cur_index_bak = cur_index;
    if(cur_index > buf_size-5)  return 0;
    else if(h264_buf[cur_index]==0x00 && h264_buf[cur_index+1]==0x00 && h264_buf[cur_index+2]==0x00 
                                 && h264_buf[cur_index+3]==0x01 && h264_buf[cur_index+4]==0x06)
    {   cur_index += 5;
        while(cur_index < buf_size){
            if(h264_buf[cur_index]==0 && h264_buf[cur_index+1]==0 && h264_buf[cur_index+2]==0 && h264_buf[cur_index+3]==1)
                break;
            else
                ++cur_index;
        }
        // printf("cur_index:%d,cur_index_bak:%d,len is:%d\n",cur_index,cur_index_bak,cur_index-cur_index_bak);
        return cur_index-cur_index_bak;   //如果找到SEI字段,返回SEI字段长度
    }
    else return cur_index-cur_index_bak;  //如果没找到SEI字段,则返回0
}

int IFramelocat(unsigned char* h264_buf, int cur_index,int buf_size){
    if(cur_index > buf_size-5)  return 0;
    else if(h264_buf[cur_index]==0x00 && h264_buf[cur_index+1]==0x00 && h264_buf[cur_index+2]==0x00 
                                 && h264_buf[cur_index+3]==0x01 && h264_buf[cur_index+4]==0x25)
        return 1;
    else return 0;
}

int PFramelocat(unsigned char* h264_buf, int cur_index,int buf_size){
    if(cur_index > buf_size-5)  return 0;
    else if(h264_buf[cur_index]==0x00 && h264_buf[cur_index+1]==0x00 && h264_buf[cur_index+2]==0x00 
                                 && h264_buf[cur_index+3]==0x01 && h264_buf[cur_index+4]==0x21)
        return 1;
    else return 0;
}

int H264DelSeiInsertSeiBeforeFrame(unsigned char* h264_buf,int buf_size,unsigned char* sei_buf,
                                                int sei_buf_size,FILE *tmp){
    for(int i=0; i<buf_size; i++){
        int len_sei = FindSei(h264_buf,i,buf_size);   //查找SEI字段位置,若当前位置为SEI开始字符,则返回SEI长度,否则返回0
        if(len_sei == 0){         //SEI字段长度为0,未找到SEI字段,原始内容直接写入
            if(IFramelocat(h264_buf,i,buf_size))   //SEI字段插入I帧之前
                fwrite(sei_buf, 1, sei_buf_size, tmp);
            else if(PFramelocat(h264_buf,i,buf_size))    //SEI字段插入P帧之前
                fwrite(sei_buf, 1, sei_buf_size, tmp);
            fwrite(&h264_buf[i], 1, 1, tmp);    //将原始码流写入文件
        }
        else if(len_sei > 0){
            i = i+len_sei-1;    //找到SEI字段,跳过码流buffer中SEI字段长度,相当于删除码流中原始SEI字段
            // --i;    //判断程序处理完之后,当前下标已经为下一个单元的起始位,for循环还会再加一,因为在此需要减一
        }
        else printf("error: findSei() return value min i!\n");
    }
    return 1;
}

int FillSeiPacket(unsigned char* sei_buffer, int sei_size, unsigned char* user_data, int user_data_size)
{
    if(sei_size-user_data_size < 25) return -1;

	unsigned char* data = (unsigned char*)sei_buffer;
    unsigned char user_data_size_high = user_data_size >> 8;    // 取用户数据长度高8位
    unsigned char user_data_size_low = user_data_size & 0xFF;   // 取用户数据长度低8位
	
    memcpy(data, SEI::start_code, sizeof(unsigned int)); // 插入4字节NALU开始码
	data += sizeof(unsigned int);   
	*data++ = SEI::nal_type; // 插入1字节NAL类型识别码,data指针偏移
	*data++ = SEI::payload_type; // 插入1字节SEI字段编码格式识别码,data指针偏移
	memcpy(data, SEI::sei_uuid, SEI::sei_uuid_size);  // 插入16字节UUID自定义识别码
	data += SEI::sei_uuid_size;   
    *data++ = user_data_size_high;  // 插入1字节用户数据长度高8位
    *data++ = user_data_size_low;  // 插入1字节用户数据长度低8位
	memcpy(data, user_data, user_data_size);    // 插入n字节用户自定义数据
	data += user_data_size;     
    sei_buffer[sei_size-1] = SEI::sei_tail;  // 插入1字节SEI封装包结尾对齐码

    if(data-sei_buffer>sei_size) return -2;
	else return 1;
}

int H264GetSeiUserdata(unsigned char* h264_buf, int h264_buf_size, unsigned char* user_buf, int user_buf_size){
    int sei_num=0,not_match_num=0;
    unsigned char* user_buf_point = user_buf;
    for(int i=0; i<h264_buf_size; i++){
        int len_sei = FindSei(h264_buf,i,h264_buf_size);    //获取码流中当前位置SEI字段长度
        if(len_sei>0){
            ++sei_num;
            int not_match_flag=0;
            for(int j=0; j<16; j++){    //判断SEI字段中UUID是否匹配
                if(h264_buf[i+6+j] == SEI::sei_uuid[j])
                    continue;
                else{
                    ++not_match_flag;
                    break;
                }
            }
            if(not_match_flag)  //如果not_match_flag为1,则该SEI字段不是我们定义的
                not_match_num += not_match_flag;
            else{   //否则该SEI是我们自定义的
                int h264_user_data_len = (h264_buf[i+22]<<8) + (h264_buf[i+23]&0xFF);
                printf("h264_buf[i+22]:%d,h264_buf[i+23]:%d,len_sei:%d\n",h264_buf[i+22],h264_buf[i+23],len_sei);
                printf("h264_user_data_len is %d\n",h264_user_data_len);
                if(h264_user_data_len!=(len_sei-25)){    //如果读取到的用户数据长度与实际测到的数据长度不匹配
                    printf("error:User data contains a stream header!\n");
                    printf("h264_buf[i+22]:%d,h264_buf[i+23]:%d,len_sei:%d\n",h264_buf[i+22],h264_buf[i+23],len_sei);
                    printf("h264_user_data_len is %d\n",h264_user_data_len);
                }
                else{   //若匹配,则正常提取用户数据
                    unsigned char* h264_usr_data = h264_buf+i+24;
                    if(h264_user_data_len > (user_buf_size+user_buf-user_buf_point)){ //判定当前写入的用户数据长度是否大于接收BUF的长度
                        printf("error:recv user_buf_size is too min! \n");
                        break;
                    }
                    memcpy(user_buf_point,h264_usr_data,sizeof(unsigned char)*h264_user_data_len);//将解析出的用户数据复制到接收BUF中
                    user_buf_point += h264_user_data_len;    //接收BUF指针偏移,方便下一帧数据写入
                    for(int k=0;k<10;k++)   //用户数据后面添加10个0作为用户数据分割标识
                       *user_buf_point++ = 0;
                }
            }
            i+=len_sei-1;
        }
        else 
            continue;
    }
    // printf("not_match_num is %d\n",not_match_num);
    return sei_num;
}


main.cpp

// #include <stdio.h>
#include <unistd.h>
#include <malloc.h>
#include "h264Process.h"

// #define H264_SIZE 1*1024*1024   //读取1MB文件大小
#define H264_SIZE 2*1024*1024   //读取0.1MB文件大小,大概包含2-3帧图像
#define USER_DATA_SIZE 512   //定义用户自定义数据大小
#define SEI_FIX_SIZE 25     //SEI封装包固有长度为25

//SEI字段插入H264码流文件程序
int insertSeiProcess(){
    //读取H264源文件,删去SEI字段 
    FILE* h264_file=NULL;
    FILE *tmp = NULL;
	static unsigned char fullBuffer[H264_SIZE+4] = {0};
	unsigned char* buffer = fullBuffer;
	h264_file = fopen("test.h264", "rb+");  //带SEI字段的H264源文件
    tmp = fopen("tmp.h264","wb+");  //写入自定义数据的的新H264文件
	if (!h264_file)
	{
		printf("ERROR:Open h264 file fialed.\n");
		return -1;
	}
	int size = fread(buffer, 1, H264_SIZE, h264_file);
    printf("h264 size:%d\n",size);

    int user_data_size = 512;
    int sei_size = user_data_size + SEI_FIX_SIZE;
    unsigned char* user_data_buf = (unsigned char*)malloc(sizeof(unsigned char)*user_data_size);
    unsigned char* sei_buf = (unsigned char*)malloc(sizeof(unsigned char)*sei_size);
    if(user_data_buf==NULL || sei_buf==NULL){
        printf("malloc faild!\n");
        return -1;
    }
    memset(user_data_buf,0,sizeof(unsigned char)*user_data_size);
    memset(sei_buf,0,sizeof(unsigned char)*sei_size);
    for(int i=0; i<100; i++){   //填充自定义数据
        user_data_buf[i]=i+1;
        // printf("%x ",user_data_buf[i]);
    }     
    // printf("\n");
    int sei_stat = FillSeiPacket(sei_buf, sei_size, user_data_buf, user_data_size);   //将用户自定义数据封装成SEI格式包
    if(sei_stat < 0) printf("fillSeiPacket faild\n");
    else printf("fillSeiPacket successed\n");
    // for(int i=0; i<sei_size;i++) printf("%x ",sei_buf[i]);
    // printf("\n");

    H264DelSeiInsertSeiBeforeFrame(buffer,size,sei_buf,sei_size,tmp);
    
    free(user_data_buf);    //释放动态内存空间
    free(sei_buf);
	fclose(h264_file);
	fclose(tmp);
}

//从H264文件中提取自定义SEI字段
int getSeiProcess(){
    FILE* h264_file = fopen("all_sei.h264","rb+");	//码流源文件
    FILE* sei_file = fopen("tmp.txt","w+");	//将读取到的自定义数据写入txt文档
    FILE* tmp1_file = fopen("h264.txt","w+");	//将H264码流数据读出来以便debug
    if (!h264_file || !sei_file){
		printf("ERROR:Open tmp.h264 or tmp.txt fialed.\n");
		return -1;
	}
    fseek(h264_file, 0, SEEK_END);      //将文件指针偏移到文件尾
    int file_size = ftell(h264_file);   //获取h264文件大小
    fseek(h264_file, 0, SEEK_SET);      //将文件指针偏移到文件头
    unsigned char* h264_buf = (unsigned char*)malloc(sizeof(unsigned char)*file_size);  //开辟h264_buf空间
    memset(h264_buf,0,sizeof(unsigned char)*file_size);     //初始化h264_buf
    int h264_size = fread(h264_buf, 1, file_size, h264_file);   //将h264文件读到h264_buf中
    printf("h264 file size is %d\n",h264_size);

    unsigned char* user_buf = (unsigned char*)malloc(sizeof(unsigned char)*h264_size);  //开辟SEI接收B空间
    memset(user_buf,0,sizeof(unsigned char)*h264_size);     //初始化h264_buf
    int find_sei_num = H264GetSeiUserdata(h264_buf, h264_size, user_buf, h264_size);
    printf("found sei num is %d\n",find_sei_num);
    for(int i=0; i<h264_size; i++)
        fprintf(tmp1_file, "%x ", h264_buf[i]);
    for(int i=0; i<file_size; i++)
        fprintf(sei_file, "%d ", user_buf[i]);

    printf("\n");
    free(h264_buf);
    free(user_buf);
    fclose(h264_file);
    fclose(sei_file);
    fclose(tmp1_file);
}

int main(int argc, char** argv)
{
    insertSeiProcess();
    //getSeiProcess();

	return 0;
}


5. 总结

以上就是H264码流中插入自定义数据的代码,H264码流文件是一个二进制文件,该代码是直接对二进制文件进行改造,不依赖于FFMPEG或者X264库。本代码中识别的I帧和P帧的帧头为25和21,常见的帧头为65和61,区别只是解码器识别该帧的重要程度,可自行修改。

转自:https://blog.csdn.net/weixin_42289213/article/details/125279309

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/687012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

商家说:我的对讲机能通话10公里、50公里,你敢信吗?

有不少渠道经销商&#xff0c;在日常经营中往往会遇到用户的经典四问&#xff1a; 您家的对讲机通话距离远吗&#xff1f;待机时间长吗&#xff1f;功能多吗&#xff1f;经得住摔吗&#xff1f; 对讲机用户常问的问题 对于后三问&#xff0c;通常还有电池电量、技术规格、功能…

查找ABAP代码

SE38执行&#xff1a;RS_ABAP_SOURCE_SCAN/RPR_ABAP_SOURCE_SCAN 批量查找字符串使用位置

互联网工程师 Java 面试题及答案整理(2023 速成版,7 天就能吃透)

现在 Java 面试都只是背答案吗&#xff1f; 不背就通过不了面试&#xff0c;但是现在面试都问原理、问场景&#xff01;Java 面试题就像我们高考时的文言文&#xff0c;包括古诗词&#xff0c;不背是不可能答出来的&#xff01;当然了&#xff0c;除了背&#xff0c;还得理解&…

全网最牛,性能测试超全流程总结整理,你都遗漏了什么...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、性能需求分析 …

Jenkins自动化构建

自动化构建 Jenkins 是一款开源 CI&CD 软件&#xff0c;用于自动化各种任务&#xff0c;包括构建、测试和部署软件 Jenkins 支持各种运行方式&#xff0c;可通过系统包、Docker 或者通过一个独立的 Java 程序 安装依赖 安装参考&#xff1a;Windows环境下安装Jenkins **…

解锁市场潜力:海外网红营销效果最大化的7个关键步骤

在当今数字化时代&#xff0c;海外网红营销已经成为许多企业推广产品和品牌的重要手段。通过与海外网红合作&#xff0c;品牌可以有效地扩大影响力和受众群体&#xff0c;并提升产品或服务的认知度。然而&#xff0c;要实现海外网红营销效果最大化&#xff0c;并不是一件轻而易…

一元函数微分学中导数--定义--意义--基本公式--运算法则

目录 导数的定义 左导数和右导数 导数的几何意义和物理意义 几何意义 导数的几何意义--切线的斜率 物理意义 导数的物理意义——瞬时速度 基本初等函数导数公式 基本初等函数 常用基本初等函数导数公式 导数求解的四则运算法则 函数的求导法则 复合函数求导法则 导…

Linux驱动入门(五)——构建第一个驱动程序

文章目录 前言开发环境配置之内核升级为什么升级内核内核升级 Hello world 驱动程序驱动模块的组成Hello World模块编译Hello World模块模块的操作Hello World模块加载后文件系统的变化 模块参数和模块之间通信模块参数模块的文件格式ELF模块之间的通信模块之间的通信实例 将模…

RHEL 9 新特性及技术演示

OpenSSH&#xff1a;新增禁止 root 的密码登录 Cockpit&#xff1a;RHEL 的 Web 控制台 DNF-3&#xff1a;软件安装方法 NetworkManager&#xff1a;网络管理的主要组件 Nftables&#xff1a;默认的用户空间防火墙 WireGuard&#xff1a;快速、安全的 VPN 隧道&#xff08…

阿里架构师珍藏版“亿级高并发系统设计手册(2023 版)”

高并发 俗话说&#xff1a;罗马不是一天建成的&#xff0c;系统的设计当然也是如此。 从原来谁都不看好的淘宝到现在的电商巨头&#xff0c;展现的不仅仅是一家互联网巨头的兴起&#xff0c;也是国内互联网行业迎来井喷式发展的历程&#xff0c;网络信号从 2G 发展到现在的 5…

【计算机图形学】期末复习Bezier曲线与曲面篇

【计算机图形学】期末复习Bezier曲线与曲面篇 文章目录 【计算机图形学】期末复习Bezier曲线与曲面篇一、Bezier曲线的定义二、一次Bezier曲线&#xff08;直线&#xff09;三、二次Bezier曲线&#xff08;抛物线&#xff09;四、三次Bezier曲线&#xff08;自由曲线&#xff0…

高效地将 TailwindCSS 与 Nuxt 结合使用

在这篇文章中&#xff0c;我们将了解如何在 TailwindCSS 的官方 Nuxt 模块的帮助下有效地将 TailwindCSS 与 Nuxt 应用程序结合使用。我们还将了解如何将 SVG 图标与 TailwindCSS 一起使用&#xff0c;而不是直接使用图像或 SVG 图标&#xff0c;以及如何基于给定图像为 Tailwi…

总结5种常用加密算法

前言 在平时的工作中&#xff0c;可能也在很多地方用到了加密、解密&#xff0c;比如&#xff1a; 用户的密码不能明文存储&#xff0c;要存储加密后的密文 用户的银行卡号、身份证号之类的敏感数据&#xff0c;需要加密传输 还有一些重要接口&#xff0c;比如支付&#xff0…

Linux conda 环境迁移 服务器之间迁移

网上很多方法语焉不详&#xff0c;本文主要介绍在Linux系统之间进行单一环境迁移&#xff0c;从服务器A迁移到服务器B的两种方式&#xff1a; conda list方式进行Linux系统在线环境迁移拷贝envs方式进行Linux系统离线环境迁移 conda list方式 迁移完毕后需要手动安装缺失的py…

ProcessOn思维导图流程图 超厉害的入门指南

如果你刚刚认识ProcessOn,不知道从哪里开始&#xff0c;希望这篇内容可以帮助到你。ProcessOn 是什么&#xff1f; ProcessOn 是一款专业的在线思维导图流程图软件。 专业强大的作图工具&#xff0c;支持多人实时在线协作&#xff0c;可用于原型图、UML、BPMN、网络拓扑图等多种…

INDEMIND双目视觉惯性模组实时生成点云并保存点云图

双目惯性相机最开始是从VINS中了解到的&#xff0c;2018年VINS中推荐过Loitor视觉惯性相机&#xff0c;但是后来看到GitHub Issue中有人反映Loitor丢帧、无技术支持等问题&#xff0c;加之购入渠道非官方故未入手Loitor&#xff0c;浏览知乎时关注到Indemind的该款产品&#xf…

AI绘画:Roop插件的特性与安装!

交叉”学科”来了&#xff01; 我们之前讲过可以实现单图换脸的Roop&#xff0c;也讲过可以通过文字描述画画的项目Stable-Diffusion-WebUI。现在这两者要通过sd-webui-roop产生交汇。 我们先来简单的看一下这个插件可以干什么&#xff01; 功能特点 根据项目作者的说法&…

JavaScript二叉树及各种遍历算法详情

目录 什么是二叉树 满二叉树完全二叉树二叉树的存储 数组存储链表存储与二叉树相关的算法 深度优先遍历广度优先遍历先序遍历中序遍历后序遍历 前言: 上一篇文章中介绍了树的概念、深度优先遍历和广度优先遍历&#xff0c;这篇文章我们来学习一个特殊的树——二叉树。 什么是…

【计算机图形学】期末复习,选择题+判断题篇

【计算机图形学】期末复习&#xff0c;选择题判断题篇 题目来源于百度、B站、中国大学慕课网&#xff0c;适用于期末复习&#xff0c;内容仅供参考&#xff0c;祝大家考试顺利通过&#xff01;&#xff01;&#xff01; 文章目录 【计算机图形学】期末复习&#xff0c;选择题判…

App压力稳定性测试之Monkey

目录 前言&#xff1a; 一、Monkey简介 二、monkey常见命令 三、日志导出 前言&#xff1a; Monkey测试是一种黑盒测试方法&#xff0c;用于测试Android应用程序的压力稳定性&#xff0c;目的是评估应用在极端情况下是否能够稳定、可靠地工作。它是Android SDK自带的一个工…