您现在的位置是:首页 > 正文

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

2024-02-01 05:43:55阅读 3

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


【声明】

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

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

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

网站文章

  • (建议收藏)Vue3 对比 Vue2.x 差异性、注意点、整体梳理,与React hook比又如何?(面试热点)

    (建议收藏)Vue3 对比 Vue2.x 差异性、注意点、整体梳理,与React hook比又如何?(面试热点)

    前言前不久Vue3的RC版本终于发布,一直没时间研究,这篇文章我将以下面的结构,为大家整体梳理下Vue3,顺便对比下vue2.x和react hook结合着带大家看看,可能有些长,耐心看完相信会有收获...

    2024-02-01 05:43:48
  • makefile 学习笔记(一)

    edit : main.o kbd.o command.o display.o /insert.o search.o files.o utils.occ -o edit main.o kbd.o co...

    2024-02-01 05:43:18
  • uboot 编译出现/bin/sh: 1: arm-linux-gnueabi-gcc: not found 问题

    出现此问题的原因可能是编译命令 $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j8。此命令中 arm-linux-gnueabi- -j8 没有正确的路径地址 将绝对路径添加上可能就好了。

    2024-02-01 05:43:10
  • HCIP课程笔记-08-3/4/5/7类LSA、OSPF特殊区域

    HCIP课程笔记-08-3/4/5/7类LSA、OSPF特殊区域

    HCIP课程笔记-08-3/4/5/7类LSA、OSPF特殊区域

    2024-02-01 05:43:03
  • 接口幂等性问题处理

    接口幂等性问题处理

    文章目录一、接口幂等性概念1. 接口调用存在的问题2. 什么是接口幂等性3. 什么情况下需要保证接口的幂等性二、那些情况需要防止任意多次执行所产生的影响均与一次执行的影响相同,这是幂等性的核心特点。其...

    2024-02-01 05:42:35
  • 【数据结构和算法】全面剖析树的各类遍历方法 热门推荐

    【数据结构和算法】全面剖析树的各类遍历方法 热门推荐

    面试中常考到树的前序,中序,后序和层序遍历,这篇博文就带你深度剖析一下二叉树的各类遍历算法的实现二叉树的遍历主要有四种,前序、中序、后序和层序遍历的实现方式主要是:递归和非递归递归遍历的实现非常容易,非递归的实现需要用到栈,难度系数要高一点。一、二叉树节点的定义二叉树的每个节点由节点值、左子树和右子树组成。class TreeNode{ public: int val; TreeNo

    2024-02-01 05:42:28
  • Mac查看本地 MySQL版本信息

    Mac查看本地 MySQL版本信息

    键入安装mysql时设置的密码 即可看到version等信息。

    2024-02-01 05:42:24
  • MySQL常用的七种join查询

    MySQL常用的七种join查询

    目录 一、INNER JION 内连接 ( A ∩ B ) 二、LEFT JOIN 左外连接( A 全有 ) 三、RIGHT JOIN 右外连接 (B 全有) ​四、FULL JOIN 全外连接( A + B)  五、LEFT Excluding JOIN  ( A - B 即 A 表独有) 六、RIGHT Excluding JOIN ( B...

    2024-02-01 05:41:55
  • XXX银行项目部署

    XXX银行项目部署 一、下载项目代码 1、使用git工具下载代码 代码路径:推荐代码下载到桌面 git clone http://sunyard_姓名拼音@bitbucket.devops.hfdev/scm/zyc/rm-code.git 2、下载完成,桌面自动生成rm-code目录 3、选中桌面rm-code,右击鼠标,选中...

    2024-02-01 05:41:48
  • IDEA插件的存储位置及编程实现

    本文介绍了IDEA插件的存储位置,并提供了通过编程查找和操作这些插件的示例代码。无论您使用的是Windows还是macOS,都可以根据相应的存储位置找到已安装的插件,并对其进行管理。本文将介绍IDEA...

    2024-02-01 05:41:40