“打破沙锅问到底”之“JPEGD”

写在前面:罗列了一些内容,试图从头到尾把JPEGD的开源样例过一遍,事无巨细,难免罗嗦,文中尚存一些不严谨、不正确的内容,欢迎各位新老朋友批评指教,多多交流

学习JPEG图片解码,看Gitee上的开源样例

https://gitee.com/ascend/samples/tree/master/cplusplus/level2_simple_inference/0_data_process/jpegd

先看开源样例的readme,总结信息如下:

jpegd的功能是将输入的jpeg图片转换为yuv图片

整体分为build和run两个步骤

build过程使用bash命令执行sample_build.sh文件

运行过后会显示

run过程使用bash命令执行sample_run.sh文件

运行过后会显示

最后,在out/output路径下就会看到yuv图片

那么,开始“问”

一步编译,一步运行,其中涉及到什么内容?

一、先看编译部分,打开sample_build.sh文件

sample_build第1行指定了使用bash命令执行

sample_build第2行定义了ScriptPath,不熟悉的话可以将双引号的内容复制到命令行里验证一下,指定ScriptPath为/scripts文件夹

sample_build第3行定义了DataPath为/data文件夹

sample_build第4行定义了common_script_dir,验证得知这是Ascend安装目录下的一个路径

sample_build第5行执行了sample_common.sh文件,查看该文件,其中只是定义了很多函数,后面涉及到再细看

sample_build第27行执行了main函数,再看7-26行的main函数包含什么内容

main第9行向屏幕打印[INFO]信息

main第11行执行了target_kernel函数,该函数是在上述sample_common.sh文件内定义的

根据该函数,若在屏幕上输入x86,则定义TargetKernel为“x86”,随后显示信息

回到sample_build.sh文件中的main函数

main第12-14行,如果上一条指令的返回值不等于零,则return 1,指的是如果上一条指令target_kernel如果没有执行成功,则该main函数返回1

main第16-18行,如果DataPath指定的/data目录下不存在dog1_1024_683.jpg文件,则使用wget命令从后面的网址下载文件并保存在/data目录下,所以首次运行之前样例文件中的/data目录下是没有待转换的jpg文件的,在sample_build.sh执行过程中完成了下载

main第20行执行了build函数,该函数是在上述sample_common.sh文件内定义的

build第99行定义了UserKernel为arch,这里我的理解是,device侧的Ascend310芯片是aarch64架构,对应这里的UserKernel,前面填入的x86是host侧,对应TargetKernel

build第100-111行指定了在TargetKernel为x86、UserKernel为arch的情况下,定义TargetCompiler为g++

build第112-114行,如果存在/build/intermediates/host文件夹,则将其删除,这是在删除上一次构建的结果

build第116-117行,创建/build/intermediates/host文件夹,并移动到该目录下

build第120行执行cmake指令进行编译,指定源文件在src文件夹,指定编译器为g++,指定跳过RPATH相关信息,其中src目录下的CMakeLists.txt有详细的参数设置

再看看src目录下的CMakeLists.txt文件

txt第4行指定了cmake的最小版本,如果系统cmake版本过低会在编译时提示

txt第7行定义了工程名称为JPEG

txt第10行指定加入编译选项-std=c++11

txt第12行设置宏定义-DENABLE_DVPP_INTERFACE,这一条是在使用dvpp功能时需要加上的

txt第15-17行指定了编译结果存放路径为/out文件夹、设置了编译debug版本时的编译选项、设置了编译release版本时的编译选项

txt第19-21行,如果环境变量中没有定义INSTALL_DIR,则返回报错信息,可以手动在.bashrc文件内查看该环境变量的定义

txt第24-27行,将/acllib/include添加到编译器头文件搜索路径下

txt第30-32行,指定第三方库搜索路径为/runtime/lib64/stub

txt第34-35行,执行编译,指定编译结果“可执行程序”名为main,指定待编译的源文件为main.cpp

txt第37-38行,链接动态库,将参数中的动态库链接到编译生成的可执行文件main上

txt第40行,使用install指令定义安装规则,参数DESTINATION制定了目标main的安装路径为txt第15行指定的位置

回到build函数

build第121-124行,如果前面的cmake没有执行成功,则该build函数返回1

build第125行执行make命令,开始编译

build第126-129行,如果前面的make没有执行成功,则打印[ERROR]信息,且该build函数返回1

build第130行,返回上一次路径,在build第117行用cd命令进入了/build/intermediates/host路径,现在返回到之前的路径下,即sample_build.sh所在的/scripts目录下;利用重定向,让cd命令的输出不打印在屏幕上

至此build函数结束,回到sample_build.sh文件中的main函数

main第21-23行,如果前面的build没有执行成功,则该main函数返回1

main第25行向屏幕打印[INFO]信息

至此sample_build.sh文件结束,编译过程结束,可以看到首次运行创建了/out目录,且out目录下包含了编译结果,即可执行文件main

二、看完编译部分,再看运行部分,打开sample_run.sh文件

sample_run第2-3行定义了ScriptPath和common_script_dir

sample_run第39行执行main函数

main第30行向屏幕打印[INFO]信息

main第32行定义running_command

main第34行执行running函数

main第35-37行,如果前面的running没有执行成功,则该main函数返回1

running第8-13行,定义Kernel,识别当前环境的信息,并定义Targetkernel

running第14-16行,进入/out路径、删除/output文件夹、新建/output文件夹

running第17行,执行running_command中描述的命令,即执行/out/main,执行时传入参数,参数为jpg文件路径

running第18-24行,根据main是否执行成功,向屏幕打印[INFO]信息或[ERROR]信息

至此sample_run.sh文件结束,可以看到最终的yuv文件存放在/out/output目录下

三、JPEGD的主要内容,打开源文件main.cpp,直奔main函数

main第207-210行:当正确输入上述running_command时,if分支不会触发,此时argc=2,argv[1]= ‘../data/dog1_1024_683.jpg’,argv[0]=’ ./main’;如果argc<2,即没有输全两个部分,或者argv[1]是个空指针,即没有输入图片路径,则会触发if分支并打印[ERROR]信息

main第211行,定义string类型变量image_path用于存储传入的图片路径

main第215-216行:初始化。首先定义指向char类型常量的指针aclConfigPath,将其作为参数调用aclInit()函数

这里为什么要把aclConfigPath定义成这个数据类型?

要从文档中查阅aclInit()函数的说明

“应用开发(C++)>AscendCL API参考>系统配置>aclInit”

从“函数功能”可知,aclInit()函数是用来进行初始化的

如果问“这个函数具体做了哪些初始化工作”,需要查看该函数的定义代码,这部分本文暂不涉及

如果问“调用这个函数进行初始化的理由是什么,写我自己的程序也必须调用这个函数吗”,需要查看文档中的JPEGD的全部工作流程,文档让怎么干,就怎么干,这部分本文计划看完代码之后再来总结,先回到main.cpp,先看这每一步都做了什么。

从“函数原型”可知,aclInit()函数的输入参数的数据类型,aclInit()函数的返回值的数据类型

根据“参数说明”以及main.cpp中定义的aclConfigPath可知,该参数是一个json配置文件的路径,打开样例代码src/acl.json可以发现,该文件几乎为空,即在当前样例中还不需要进行具体的配置

main第217行调用INFO_LOG函数打印信息

该函数在头文件main.h中涉及

main第219行,调用aclrtSetDevice()函数,指定要使用的Device

该函数传入int32_t类型的参数deviceId_,该参数在main.h中涉及

main第220行调用INFO_LOG函数打印信息

main第222行调用aclrtCreateContext()函数,显示创建一个Context

该函数同样传入了deviceId_,同时还需要传入一个指向aclrtContext类型变量的指针,变量context_同样在main.h中有涉及,我猜测context_本身就是一个空指针,这里传入的是指针的指针&context_,具体细节还要从aclrtContext数据类型的定义处核实;执行完该函数后,该指针将指向创建好的Context

main224行调用aclrtCreateStream()函数,创建一个Stream

该函数传入一个指向aclrtStream类型变量的指针,该参数在main.h中如下,执行完函数之后,该指针将指向创建好的Stream

main第225行调用aclrtGetRunMode()函数,获取运行模式

该函数传入一个指向aclrtMode类型变量的指针,该参数在main.h中如下,执行完函数之后,该指针将指向ACL_DEVICE,这是因为前面提到host为x86,device为aarch64

main第226行调用INFO_LOG函数打印信息

main第228-232行,创建了图片描述信息,PicDesc类型变量testPic,并打印了信息

数据类型PicDesc在main.h中有描述

结合打印信息可知,结构体testPic中的成员picName为前述image_path,借由c_str()函数将string类型的image_path转换为char*类型,详见c_str()函数

main第236-241行,调用GetDeviceBufferOfPicture()函数

GetDeviceBufferOfPicture第30-33行,检查是否传入有效的图片路径

GetDeviceBufferOfPicture第35-39行,以rb权限打开图片

GetDeviceBufferOfPicture第41-43行,首先将指针fp指向文件末尾,然后调用ftell()函数计算二进制文件的大小,最后将指针fp指向文件开始处

GetDeviceBufferOfPicture第45-47行,用new分配一个char数组,并返回指向第一个char的指针inputBuffer

GetDeviceBufferOfPicture第48行,调用fread()函数,从inputBuffer开始,以sizeof(char)为单位,在fp流中读取inputBuffSize个单位,返回成功读取的对象数给readSize

GetDeviceBufferOfPicture第49-55行,检查读取是否正常完成

GetDeviceBufferOfPicture第57-68行,调用acldvppJpegGetImageInfoV2()函数,读取jpeg图片的宽、高、通道数、编码格式,然后调用INFO_LOG函数向屏幕打印出这些信息

GetDeviceBufferOfPicture第72-78行,调用acldvppJpegPredictDecSize()函数,利用传入的jpg图片预测解码后yuv图片所需的内存,并检查是否完成计算

其中,输入参数PIXEL_FORMAT_YUV_SEMIPLANAR_420是定义好的

GetDeviceBufferOfPicture第80-87行,调用acldvppMalloc()函数,根据host侧存储数据时占用的缓存大小inputBufferSize,进行“内存首地址128对齐”,计算出需要给Device侧分配的内存大小,然后传给inBufferDev

GetDeviceBufferOfPicture第89-102行,根据runMode,调用aclrtMemcpy()函数,将数据从host侧复制到device侧。其中第一个参数inBufferDev为上一步创建好的device侧的缓存;第二个参数为device侧“目的内存地址的最大内存长度”,样例中用的是inputBufferSize,图片在host侧的内存占用长度。第三个参数inputBuffer为host侧的存放图片的首地址指针。第四个参数inputBufferSize为内存复制的长度,需要将图片的全部长度复制过去。第五个参数制定了内存复制类型,即从host向device复制

 GetDeviceBufferOfPicture第104行,在完成复制之后,删除了host侧原本存放图片的内存

GetDeviceBufferOfPicture第105行,传出device侧图片内存占用长度devPicBufferSize

GetDeviceBufferOfPicture第106行,关闭用于读取图片文件的流fp

GetDeviceBufferOfPicture第107行,返回device存有图片的内存首地址指针inBufferDev

关于其中内存对齐要求,来源于文档中的约束说明

 

至此GetDeviceBufferOfPicture()函数结束,main函数237行通过调用该函数,得到了device侧的内存首地址指针picDevBuffer,得到了device侧的内存大小devPicBufferSize

继续回到main函数

main第243行,调用acldvppCreateChannelDesc()函数,创建通道描述符

main第244行,调用acldvppCreateChannel()函数,创建数据处理通道,创建后该通道描述符将对应实际存在的通道信息

main第245行,向屏幕打印信息

main第248行,调用SetInput()函数,定义得到了device侧内存首地址指针inDevBuffer_、device侧内存长度inDevBufferSize_、图片宽度inputWidth_、图片高度inputHeight_

main第251-252行,根据对齐规则,计算decodeOutWidthStride和decodeOutHeightStride,对齐规则参考文档中的约束说明

main第257-261行,调用acldvppMalloc()函数,根据图片描述信息结构体testPic的成员jpegDecodeSize,在device侧分配内存,并传给decodeOutDevBuffer_

main第263-267行,调用acldvppCreatePicDesc()函数,创建图片描述符,返回acldvppPicDesc类型的指针

main第269-276行调用acldvppSetPicDesc系列函数,设置Device上的数据内存、图片格式、原图宽高、对齐后宽高、解码后的内存大小

main第278行调用acldvppJepgDecodeAsync()函数,执行异步解码。第一个参数为通道描述符,第二个参数为device侧内存首地址指针,第三个参数device侧内存大小,第四个参数为解码输出图片描述符,第五个参数为stream

 

main第285行调用aclrtSynchronizeStream()函数,阻塞应用程序的运行,直到传入的Stream执行完毕

main第291行调用acldvppGetPicDescSize()函数,传入解码后的图片描述符,返回图片数据的内存的实际大小,赋值给decodeDataSize_

main第293-294行调用acldvppFree()函数,释放device侧用于存放解码前原图的内存,然后将指向该内存的指针改为空指针

main第296行使用string类型的find函数,定义dir_tail_index为“/data”在image_path中的位置,当初传入的image_path为“../data/dog1_1024_683.jpg”,位置为2

main第297行定义string类型变量outfile_dir,使用string类型的substr函数,从位置0开始,向后一共选取dir_tail_index=2个,返回这些字符,与后面的“/”和“out/output/”连起来,最终得到“../out/output/”

main第298-299行定义string类型变量outfile_path,先使用string类型的rfind函数,从后往前,返回“.jpg”在字符串image_path中的位置,然后使用substr函数返回“dog1_1024_683”,最后再利用输入图片描述信息中的宽高信息,共同组成outfile_path

main第300行调用INFO_LOG函数将其打印在屏幕上

main第303-307行调用SaveDvppOutputData()函数

SaveDvppOutputData第120-124行以wb+权限打开fileName,即yuv文件,并检查是否正常

SaveDvppOutputData第125行因RunMode==ACL_HOST进入分支

SaveDvppOutputData第126-132行,调用aclrtMallocHost()函数,在host上运行时申请host侧内存,将申请到的内存的指针的指针赋值给参数hostPtr,申请内存的大小依据解码后的图片内存大小decodeDataSize_

SaveDvppOutputData第134-140行,调用aclrtMemcpy()函数,将解码后的图片数据从device侧复制到host侧

SaveDvppOutputData第141-148行,调用fwrite()函数,第一个参数hostPtr为host侧用于存放解码后文件的地址指针,第二个参数sizeof(char)为“写入单位”,第三个参数dataSize为写入单位数量,第四个参数outFileFp为用于存放host侧解码后图片yuv文件的流

SaveDvppOutputData第149行,调用aclrtFreeHost()函数,释放host侧用于存放解码后二进制文件的内存

SaveDvppOutputData第160行调用fflush函数进行输出刷新,将流outFileFp中未被写入文件的信息强行写入

SaveDvppOutputData第161行调用fclose函数关闭流

至此SaveDvppOutputData函数结束,该函数主要将device侧的缓存复制到了host侧,然后将其写入到了yuv格式的文件中

再回到main函数

main第309行调用DestroyResource()函数

DestroyResource第169-172行,调用acldvppDestroyPicDesc()函数释放输出图片描述符

DestroyResource第174-180行,调用aclrtDestroyStream()函数释放Stream

DestroyResource第181行,调用INFO_LOG函数向屏幕打印信息

DestroyResource第183-190行,调用aclrtDestroyContext()函数释放Context,调用INFO_LOG函数向屏幕打印信息

DestroyResource第192-196行,调用aclrtResetDevice ()函数释放Device,调用INFO_LOG函数向屏幕打印信息

DestroyResource第198-202行,调用aclFinalize ()函数去初始化,调用INFO_LOG函数向屏幕打印信息

至此DestoryResource()函数结束

 

再回到main函数

main第311行打印信息

至此main.cpp结束

四、总结main.cpp做了什么事情

参看文档“应用开发(C++)>接口调用流程>媒体数据处理V1>JPEGD图片解码”

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值