首先感谢阅读,如果您也对TDA4相关的开发感兴趣,我们这边有个学习交流微信群,可以入群和大家一起交流学习。
资历较浅,水平有限,如遇错误,请大家多指正!
保持开源精神,共同分享、进步!
博主WX : AIR_12 我会拉你入群。
链接:TDA4 相关专栏 链接:TDA4 Demo Gitee开源库
欢迎大家加入,一起维护这个开源库,给更多的朋友提供帮助。
上一篇文章完成了YUV NV12 编码encode H264视频的功能。
[TI TDA4 J721E]USB摄像头 YUV图像视频编码生成h264 encode节点的使用_AIRKernel的博客-CSDN博客
这一篇博客将尝试逆向decode,将H264视频流,解码为YUV NV12格式的图像序列。同样基于版本是0703,原因请查看上一篇博客。
一、H264简介
在进行decode解码之前,首先要了解什么是H264。
详细的文章可以参考这个博客(转载):H264--2--语法及结构_杨重选的专栏-CSDN博客_h264 结构
其中最重要也是最基础的部分就是 I帧、P帧、B帧。
一个I帧+若干P帧+若干B帧,形成GOP(group of pictures)。
视频以GOP形式,流式发送,每一次发送的数据都是单独的帧(I、P、B)。
I帧是关键帧,只有收到I帧以后,这个GOP才能有效的被解码,否则此GOP无效,将被丢弃。
在程序里,为了方便,我将encode节点作为一个graph;decode作为一个graph;分开单独运行。
流程如下:(大家在走读代码的时候,一定要细心一点,读懂代码才能理解。)
1、encode node 产生 bitStream(H264视频流)
2、decode node 对此bitStream进行解码。
二、pipeline的应用及相关说明
tiovx为了提高硬件的使用效率和整体的执行速度,有一个流水线pipeline的相关操作。
官方资料入口:
比较关键的几个函数如下:
1、vxGraphParameterEnqueueReadyRef //入队参数
2、vxGraphParameterDequeueDoneRef //出队参数,执行此函数,则默认graph已经被执行。(这里具体要去看tiovx里面的pipeline理解一下,理解了这里,基本上pipeline就理解了一半了)。
3、tivxSetUserDataObjectAttribute //设置入队的用户数据对象的大小。
这几个核心函数主要在如下的程序段内使用:
vx_status app_run_decodeGraph(vx_graph graph, DecodeObj *decodeObj, vx_user_data_object *bitStream)
{
vx_status status = VX_SUCCESS;
if (decodeObj->pipeline < 0)
{
/* Enqueue output */
vxGraphParameterEnqueueReadyRef(graph, decodeObj->output_image_graph_parameter_index, (vx_reference *)&decodeObj->output_image[decodeObj->enqueueCnt], 1);
app_copy_encodeBitStream_to_decodeBitStream(&decodeObj->bitstream_obj[decodeObj->enqueueCnt], bitStream);
/* Enqueue input - start execution */
vxGraphParameterEnqueueReadyRef(graph, decodeObj->input_bitstream_graph_parameter_index, (vx_reference *)&decodeObj->bitstream_obj[decodeObj->enqueueCnt], 1);
decodeObj->enqueueCnt++;
decodeObj->enqueueCnt = (decodeObj->enqueueCnt >= decodeObj->num_buf) ? 0 : decodeObj->enqueueCnt;
decodeObj->pipeline++;
}
else if (decodeObj->pipeline >= 0)
{
vx_int32 array_idx = -1, img_array_idx = -1;
vx_image out_image;
vx_user_data_object in_bitstream;
uint32_t num_refs;
/* Dequeue & Save output */
//参数说明:要出队的参数索引、被出队的对象填充该区域、最大的出队个数、实际的出队个数
vxGraphParameterDequeueDoneRef(graph, decodeObj->output_image_graph_parameter_index, (vx_reference *)&out_image, 1, &num_refs);
app_find_image_array_index(decodeObj->output_image, (vx_reference)out_image, decodeObj->num_buf, &img_array_idx);
if (img_array_idx != -1)
{
app_decode_saveImageToFile("/home/root/mydecode.yuv", &decodeObj->output_image[img_array_idx]);
}
/* Dequeue input */
vxGraphParameterDequeueDoneRef(graph, decodeObj->input_bitstream_graph_parameter_index, (vx_reference *)&in_bitstream, 1, &num_refs);
/* Enqueue output */
vxGraphParameterEnqueueReadyRef(graph, decodeObj->output_image_graph_parameter_index, (vx_reference *)&out_image, 1);
app_find_user_object_array_index(decodeObj->bitstream_obj, (vx_reference)in_bitstream, decodeObj->num_buf, &array_idx);
if (array_idx != -1)
{
app_copy_encodeBitStream_to_decodeBitStream(&decodeObj->bitstream_obj[array_idx], bitStream);
}
/* Enqueue input - start execution */
vxGraphParameterEnqueueReadyRef(graph, decodeObj->input_bitstream_graph_parameter_index, (vx_reference *)&in_bitstream, 1);
}
return status;
}
三、开始移植decode节点
特别注意:decoder requires the output size to be multiple of 64.(这里是个坑,需要注意一下)。
decode节点的输出图像尺寸,需要高度是64的整数倍!!!(后面在拷贝图像的时候,只拷贝前面有用的部分)
比如:
A、你的USB摄像头是1280*720;那需要设置decode节点输出图像的高度为768,才是64的整数倍。在初始化的时候,代码如下:
if(status == VX_SUCCESS)
{
obj->decodeObj.height = 768;
obj->decodeObj.width = 1280;
app_init_decode(obj->context, &obj->decodeObj);
}
B、decode解码成功以后,只需要复制out image图像的前面 1280 * 720 部分的图像即可!!!
见函数:代码如下:
vx_status app_decode_saveImageToFile(char *filename , vx_image *image)
{
vx_status status = VX_SUCCESS;
vx_uint32 width, height;
vx_imagepatch_addressing_t image_addr;
vx_rectangle_t rect;
vx_map_id map_id,map_id1;
void *data_ptr,*data_ptr1;
if (NULL != filename) //判断保存的路径是否为空
{
vxQueryImage(*image, VX_IMAGE_WIDTH, &width, sizeof(vx_uint32));
vxQueryImage(*image, VX_IMAGE_HEIGHT, &height, sizeof(vx_uint32));
width=1280;
height = 720;
rect.start_x = 0;
rect.start_y = 0;
rect.end_x = width;
rect.end_y = height;
status = vxMapImagePatch(*image,
&rect,
0,
&map_id,
&image_addr,
&data_ptr,
VX_WRITE_ONLY,
VX_MEMORY_TYPE_HOST,
VX_NOGAP_X);
if (status == (vx_status)VX_SUCCESS)
{
rect.start_x = 0;
rect.start_y = 0;
rect.end_x = width;
rect.end_y = height / 2;
status = vxMapImagePatch(*image,
&rect,
1,
&map_id1,
&image_addr,
&data_ptr1,
VX_WRITE_ONLY,
VX_MEMORY_TYPE_HOST,
VX_NOGAP_X);
}
FILE *fp = fopen(filename, "a+");
if (fp != NULL)
{
size_t ret;
ret = fwrite(data_ptr, 1, width * height, fp);
if (ret != width * height)
{
printf("# ERROR: Unable to write data to file [%s]\n", filename);
}
ret = fwrite(data_ptr1, 1, width * height/2, fp);
if (ret != width * height/2)
{
printf("# ERROR: Unable to write data to file [%s]\n", filename);
}
fclose(fp);
}
else
{
printf("# ERROR: Unable to open file for writing [%s]\n", filename);
status = VX_FAILURE;
}
vxUnmapImagePatch(*image, map_id);
vxUnmapImagePatch(*image, map_id1);
}
else
{
status = VX_FAILURE;
return status;
}
return status;
}
OK。开始移植了。
大家最好是能够下载我提供的源码,然后跟着源码走读,这样比较方便一点。因为这里可能讲的不是很清楚,源码最直观。(下载链接在文章末尾)
1、创建两个文件,C 和 H,在头文件里创建一个结构体,用于说明在decode时,用到的各个变量。
2、创建初始化函数:app_init_decode;主要对各个用户变量进行创建、初始化等操作。(在 app_init 里调用此函数)
vx_status app_init_decode(vx_context context, DecodeObj *decodeObj)
{
vx_status status = VX_SUCCESS;
/* Create object for encode parameters */
tivx_video_decoder_params_init(&decodeObj->params); //初始化参数
decodeObj->params.bitstream_format = TIVX_BITSTREAM_FORMAT_H264; //设置解码格式为H264
decodeObj->configuration_obj = vxCreateUserDataObject(context,
"tivx_video_decoder_params_t",
sizeof(tivx_video_decoder_params_t),
NULL);
vxCopyUserDataObject(decodeObj->configuration_obj,
0,
sizeof(tivx_video_decoder_params_t),
&decodeObj->params,
VX_WRITE_ONLY,
VX_MEMORY_TYPE_HOST);
if (vxGetStatus((vx_reference)decodeObj->configuration_obj) != VX_SUCCESS)
{
APP_PRINTF("configuration_obj create failed\n");
return VX_FAILURE;
}
decodeObj->num_buf = MAX_NUM_BUF;
decodeObj->pipeline_depth = MAX_NUM_BUF;
int i = 0;
for (i = 0; i < decodeObj->num_buf; i++)
{
decodeObj->bitstream_obj[i] = vxCreateUserDataObject(context, "video_bitstream", decodeObj->width * decodeObj->height * 3 / 2, NULL);
decodeObj->output_image[i] = vxCreateImage(context, decodeObj->width, decodeObj->height, VX_DF_IMAGE_NV12);
}
sprintf(decodeObj->outPutfile,"/home/root/decode_graph.yuv");
decodeObj->outPut_fp = fopen(decodeObj->outPutfile,"w+"); //打开文件
if (NULL == decodeObj->outPut_fp) //如果打开文件失败,则提示失效
{
status = VX_FAILURE;
printf("Decode : open file %s failed!\n", decodeObj->outPutfile);
}
return status;
}
3、创建graph创建函数:app_create_graph_decode:主要将相关的参数索引添加到Graph内。(在 app_create_graph内调用此函数)
vx_status app_create_graph_decode(vx_graph graph, DecodeObj *decodeObj, vx_user_data_object *bitstream_obj)
{
vx_status status = VX_SUCCESS;
decodeObj->node = tivxVideoDecoderNode(graph,
decodeObj->configuration_obj,
bitstream_obj[0],
decodeObj->output_image[0]);
vxSetNodeTarget(decodeObj->node, VX_TARGET_STRING, TIVX_TARGET_VDEC1); //设置node Target目标核
vxSetReferenceName((vx_reference)decodeObj->node, "Decode_node");
status = vxGetStatus((vx_reference)decodeObj->node);
vx_graph_parameter_queue_params_t graph_parameters_queue_params_list[2];
int graph_parameter_num = 0;
add_graph_parameter_by_node_index(graph, decodeObj->node, 1);
decodeObj->input_bitstream_graph_parameter_index = graph_parameter_num;
graph_parameters_queue_params_list[graph_parameter_num].graph_parameter_index = graph_parameter_num;
graph_parameters_queue_params_list[graph_parameter_num].refs_list_size = decodeObj->num_buf;
graph_parameters_queue_params_list[graph_parameter_num].refs_list = (vx_reference *)&decodeObj->bitstream_obj[0];
graph_parameter_num++;
add_graph_parameter_by_node_index(graph, decodeObj->node, 2);
decodeObj->output_image_graph_parameter_index = graph_parameter_num;
graph_parameters_queue_params_list[graph_parameter_num].graph_parameter_index = graph_parameter_num;
graph_parameters_queue_params_list[graph_parameter_num].refs_list_size = decodeObj->num_buf;
graph_parameters_queue_params_list[graph_parameter_num].refs_list = (vx_reference *)&decodeObj->output_image[0];
graph_parameter_num++;
vxSetGraphScheduleConfig(graph,
VX_GRAPH_SCHEDULE_MODE_QUEUE_AUTO,
graph_parameter_num,
graph_parameters_queue_params_list);
tivxSetGraphPipelineDepth(graph, decodeObj->pipeline_depth);
decodeObj->pipeline = -decodeObj->num_buf;
decodeObj->enqueueCnt = 0;
return status;
}
4、创建运行graph函数:app_run_decodeGraph:这个函数是每执行一次解码,都会执行一次。(这个在app_run_graph_for_one_frame 函数里面调用了app_run_decodeGraph 函数)
主要功能:
A、当pipeline小于0时,此时正在缓冲两帧数据(因为P帧需要参考前帧才能进行解码)。
B、当pipeline大于0,进入到正式的解码流程 ;出队操作,将前面缓冲的两帧数据进行解码,输出一帧YUV图像;(出队操作执行时,默认创建的graph已经被执行了,这里很重要!!!也就是已经解码完成,输出YUV图像了。)
C、将解码完成的图像,写入文件内。(app_decode_saveImageToFile)
D、然后再次入队两个参数。循环执行pipeline大于0 的这个循环。
先入队output_image,再将encode生成的bitStream 拷贝到 decode的输入 bitStream内。(这里需要将用户定义的对应映射到内存,然后进行内存操作,看下一点 E)。
vx_status app_run_decodeGraph(vx_graph graph, DecodeObj *decodeObj, vx_user_data_object *bitStream)
{
vx_status status = VX_SUCCESS;
if (decodeObj->pipeline < 0)
{
/* Enqueue output */
vxGraphParameterEnqueueReadyRef(graph, decodeObj->output_image_graph_parameter_index, (vx_reference *)&decodeObj->output_image[decodeObj->enqueueCnt], 1);
app_copy_encodeBitStream_to_decodeBitStream(&decodeObj->bitstream_obj[decodeObj->enqueueCnt], bitStream);
/* Enqueue input - start execution */
vxGraphParameterEnqueueReadyRef(graph, decodeObj->input_bitstream_graph_parameter_index, (vx_reference *)&decodeObj->bitstream_obj[decodeObj->enqueueCnt], 1);
decodeObj->enqueueCnt++;
decodeObj->enqueueCnt = (decodeObj->enqueueCnt >= decodeObj->num_buf) ? 0 : decodeObj->enqueueCnt;
decodeObj->pipeline++;
}
else if (decodeObj->pipeline >= 0)
{
vx_int32 array_idx = -1, img_array_idx = -1;
vx_image out_image;
vx_user_data_object in_bitstream;
uint32_t num_refs;
/* Dequeue & Save output */
//参数说明:要出队的参数索引、被出队的对象填充该区域、最大的出队个数、实际的出队个数
vxGraphParameterDequeueDoneRef(graph, decodeObj->output_image_graph_parameter_index, (vx_reference *)&out_image, 1, &num_refs);
app_find_image_array_index(decodeObj->output_image, (vx_reference)out_image, decodeObj->num_buf, &img_array_idx);
if (img_array_idx != -1)
{
app_decode_saveImageToFile("/home/root/mydecode.yuv", &decodeObj->output_image[img_array_idx]);
}
/* Dequeue input */
vxGraphParameterDequeueDoneRef(graph, decodeObj->input_bitstream_graph_parameter_index, (vx_reference *)&in_bitstream, 1, &num_refs);
/* Enqueue output */
vxGraphParameterEnqueueReadyRef(graph, decodeObj->output_image_graph_parameter_index, (vx_reference *)&out_image, 1);
app_find_user_object_array_index(decodeObj->bitstream_obj, (vx_reference)in_bitstream, decodeObj->num_buf, &array_idx);
if (array_idx != -1)
{
app_copy_encodeBitStream_to_decodeBitStream(&decodeObj->bitstream_obj[array_idx], bitStream);
}
/* Enqueue input - start execution */
vxGraphParameterEnqueueReadyRef(graph, decodeObj->input_bitstream_graph_parameter_index, (vx_reference *)&in_bitstream, 1);
}
return status;
}
E、其中有一个用户参数相互拷贝的问题,代码如下:
功能:将两个用户数据都进行vxMapUserDataObject内存映射,然后将源数据,拷贝到目标数据,完成数据的交换。
这里是将输入的encode bitStream,拷贝到等待解码的decode bitStream内。
//拷贝encode的比特流,到decode中,等待执行
vx_status app_copy_encodeBitStream_to_decodeBitStream(vx_user_data_object *de_bitstream_obj, vx_user_data_object *en_bitstream_obj)
{
vx_status status = VX_SUCCESS;
vx_map_id map_id,en_map_id;
vx_size en_bitstream_size;
uint8_t *bitstream, *en_bitstream;
//查询encode bitstream的流大小
status = vxQueryUserDataObject(*en_bitstream_obj,
TIVX_USER_DATA_OBJECT_VALID_SIZE,
&(en_bitstream_size), sizeof(vx_size));
APP_ASSERT(status == VX_SUCCESS);
/* Fill the input buffer. */ //向内存映射出bitstream_obj的内存地址,大小和输入流大小相同
status = vxMapUserDataObject(*de_bitstream_obj, 0,
en_bitstream_size,
&map_id, (void *)&bitstream,
VX_READ_ONLY, VX_MEMORY_TYPE_HOST, 0);
APP_ASSERT(status == VX_SUCCESS);
status = vxMapUserDataObject(*en_bitstream_obj, 0,
en_bitstream_size,
&en_map_id, (void *)&en_bitstream,
VX_READ_ONLY, VX_MEMORY_TYPE_HOST, 0);
APP_ASSERT(status == VX_SUCCESS);
APP_PRINTF("copying bitstream....................\n");
memcpy(bitstream, en_bitstream, en_bitstream_size);
APP_PRINTF("copying bitstream done! encodeSize = %ld\n", en_bitstream_size);
vxUnmapUserDataObject(*de_bitstream_obj, map_id);
vxUnmapUserDataObject(*en_bitstream_obj, en_map_id);
tivxSetUserDataObjectAttribute(*de_bitstream_obj, TIVX_USER_DATA_OBJECT_VALID_SIZE, (void *)&en_bitstream_size, sizeof(vx_size));
return status;
}
具体的主进程内部的调用,大家自行下载完整的代码包。编译测试。
再强调一次:注意:decoder requires the output size to be multiple of 64.
完整代码地址:
[TITDA4J721E]USB摄像头YUV图像获取EncodeDecode节点使用和移植disp显示-编解码文档类资源-CSDN下载
放入vision_apps/basic_demos/下,即可进行编译。
四、运行验证
1、直接在 vision_apps目录下
sudo make -j16 |grep error
2、然后执行网络拷贝脚本(IP地址是开发板地址,直接在vision_apps目录下执行此脚本即可将必要的.out文件拷贝到开发板的对应目录下了。)
网络操作,可以查看这篇博客:[TI TDA4 J721E]开发板网络调试功能及开机自动配置网络_AIRKernel的博客-CSDN博客
#!/bin/sh
sudo scp ./out/J7/A72/LINUX/release/*.out root@192.168.1.188:/opt/vision_apps
sudo scp ./out/J7/C66/SYSBIOS/release/*.out root@192.168.1.188:/lib/firmware/vision_apps_evm/
sudo scp ./out/J7/C71/SYSBIOS/release/*.out root@192.168.1.188:/lib/firmware/vision_apps_evm/
sudo scp ./out/J7/R5F/SYSBIOS/release/*.out root@192.168.1.188:/lib/firmware/vision_apps_evm/
exit 0
3、利用ssh登录开发板,进入/opt/vision_apps/目录下
ssh root@192.168.1.188
cd /opt/vision_apps
4、创建一个配置文件app_usb_disp.cfg,内容如下:
# location of conifg
tidl_config /opt/vision_apps/test_data/psdkra/tidl_models/tidl_io_peele_300_1.bin
# location of network
tidl_network /opt/vision_apps/test_data/psdkra/tidl_models/tidl_net_peele_300.bin
# location of input files
input_file_path /opt/vision_apps/test_data/psdkra/tidl_demo_images
# location of output files
output_file_path ./app_tidl_od_out
# start frame Number
start_frame 500
# number of frames
num_frames 400
# input size (width height)
in_size 1024 512
# size given to DL network (width height)
# This should should not be less than 4x of input width or height
dl_size 1024 512
# size given for display (width height)
# This should should not be less than 4x of input width or height
out_size 1024 512
# vizualization threshold
viz_th 0.95
# Maximum number of Object Detection classes
num_classes 90
# delay in milli seconds (max 2000ms)
delay_in_msecs 0
# Enable or disable output image writing. 1 Enables it , 0 disables it
en_out_img_write 0
# If 1 - Enable display 0 - Disable display
display_option 1
# number of iterations to loop the inputs
num_iterations 1
# interactive input mode 1: yes, 0: no
is_interactive 1
5、创建好以后,执行以下指令即可:
./vx_app_usb_disp.out --cfg app_usb_disp.cfg
6、将生成的文件保存在/home/root/mydecode.yuv。使用scp将文件拷贝出来,使用没player播放测试。
scp /home/root/mydecode.yuv ubuntu@192.168.1.119:/home/ubuntu/mydecode.yuv
7、mplayer -demuxer rawvideo -rawvideo w=1280:h=720:format=nv12 mydecode.yuv -fps 30
fps后面跟的和视频采样有关,如果视频采样是30帧/S,那是正常速度播放,如果是15帧/秒,那就是快一倍的播放。
8、验证结果
完整代码地址:
[TITDA4J721E]USB摄像头YUV图像获取EncodeDecode节点使用和移植disp显示-编解码文档类资源-CSDN下载
PS:解码的YUV图像可能还有些缺陷,这个正在查找原因,有时候UV分量不正确,正在查找这个bug。大家如果发现问题,欢迎私信我,我会及时更正过来。(已经修复)
【声明】
【欢迎转载转发,请注明出处。原创比较辛苦,请尊重原创,祝大家学习愉快!】
【博主专注嵌入式开发,具有多年嵌入式软、硬件开发经验,欢迎大家学习交流!】
【如有嵌入式相关项目需求,欢迎私信】