[TI TDA4 J721E]]Decode节点解码H.264视频流生成NV12 YUV图像 使用和移植

          首先感谢阅读,如果您也对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。大家如果发现问题,欢迎私信我,我会及时更正过来。(已经修复)


【声明】

【欢迎转载转发,请注明出处。原创比较辛苦,请尊重原创,祝大家学习愉快!】

【博主专注嵌入式开发,具有多年嵌入式软、硬件开发经验,欢迎大家学习交流!】

【如有嵌入式相关项目需求,欢迎私信】

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值