FFmpeg实时解码H264

    ffmpeg的解码过程在前面已经稍微总结了下,这里主要是测试一下用ffmpeg如何进行实时的解码。

    在解码之前,我们先做好准备工作,调用摄像头。编码的过程中,进行入队出队操作,出队后的数据交给解码器,进行解码。

    接下来依次介绍各个模块。


1.调用摄像头:

[cpp]  view plain  copy
  1. VideoCapture capture(0);  
  2.   
  3. int w = capture.get(CV_CAP_PROP_FRAME_WIDTH);  
  4. int h = capture.get(CV_CAP_PROP_FRAME_HEIGHT);  
  5. int yuv_bufLen = w * h * 3 / 2;  
  6. unsigned char* pYuvBuf = new unsigned char[yuv_bufLen];  
  7.   
  8. cout << "Frame size : " << w << " x " << h << endl;  
  9. namedWindow("opencamera", CV_WINDOW_AUTOSIZE);  
  10.   
  11. while (1)  
  12. {  
  13.     Mat frame;  
  14.     capture >> frame;  
  15.     imshow("opencamera", frame);  
  16.     if (waitKey(30) == 27) break;         
  17. }  
    怎么利用opencv调用摄像头,这里不做过多介绍,可以参考这里: 点击打开链接

2.编码过程:

[cpp]  view plain  copy
  1. DWORD WINAPI x264_encode(LPVOID lparam)  
  2. {  
  3.     VideoCapture capture(0);  
  4.     if (!capture.isOpened())  
  5.     {  
  6.         cout << "Cannot open the video cam" << endl;  
  7.         return -1;  
  8.     }  
  9.   
  10.     int w = capture.get(CV_CAP_PROP_FRAME_WIDTH);  
  11.     int h = capture.get(CV_CAP_PROP_FRAME_HEIGHT);  
  12.   
  13.     result_link_type* result_link = (result_link_type*)lparam;  
  14.   
  15.     int yuv_bufLen = w * h * 3 / 2;  
  16.     //unsigned char* pYuvBuf = new unsigned char[yuv_bufLen];  
  17.   
  18.     //int fps =25;  
  19.     size_t yuv_size = w * h * 3 / 2;  
  20.     x264_t *encoder;  
  21.     x264_picture_t pic_in, pic_out;  
  22.     uint8_t *yuv_buffer;  
  23.   
  24.     x264_param_t param;  
  25.     //x264_param_default_preset(¶m, "veryfast", "zerolatency");  //为结构体param赋默认值  
  26.     x264_param_default_preset(¶m, "veryfast""animation");  
  27.   
  28.     //param.i_threads = 1;         //并行编码多帧  
  29.     param.i_width = w;           //视频图像的宽  
  30.     param.i_height = h;  
  31.                                                                                                                                                                                                                                                                     //  param.i_fps_num = fps;       //帧率分子  
  32.     //param.i_fps_den = 1;         //帧率分母 , fps_num / fps_den = 帧率  
  33.     //param.i_keyint_max = 50;     //IDR帧之间的间隔  
  34.     //param.b_intra_refresh = 1;   //是否使用周期帧内刷新IDR帧  
  35.     //param.b_annexb = 1;          //加前缀码0x00000001  
  36.     //param.rc.b_mb_tree = 0;      //实时编码必须为0,否则有延迟  
  37.     //param.b_sliced_threads = 0;  
  38.   
  39.     //x264_param_apply_profile(¶m, "baseline");    //编码器的参数,使用baseline编码,可以跟上面的参数做冲突比较  
  40.   
  41.     encoder = x264_encoder_open(¶m);     //打开编码器,初始化param  
  42.   
  43. #if 1  
  44.     x264_picture_alloc(&pic_in, X264_CSP_I420, w, h);    //为pic_in分配内存   
  45.     yuv_buffer = (uint8_t*)malloc(yuv_size);     //给yuv_buffer分配内存  
  46.   
  47.     pic_in.img.plane[0] = yuv_buffer;       //pic_in的三通道分别赋值  
  48.     pic_in.img.plane[1] = pic_in.img.plane[0] + w * h;  
  49.     pic_in.img.plane[2] = pic_in.img.plane[1] + w * h / 4;  
  50.   
  51.     int64_t i_pts = 0;  
  52.     x264_nal_t *nals;  
  53.     int nnal;  
  54.   
  55.     FILE *fp_out = fopen("test.h264""wb");  
  56.     if (!fp_out)  
  57.     {  
  58.         printf("Could not open output 264 file\n");  
  59.         return -1;  
  60.     }  
  61.   
  62. #if 1  
  63.     FILE* pFileOut = fopen("test.yuv""w+");  
  64.     if (!pFileOut)  
  65.     {  
  66.         printf("Could not open input yuv file\n");  
  67.         return -1;  
  68.     }  
  69. #endif  
  70.   
  71.     cout << "Frame size : " << w << " x " << h << endl;  
  72.     namedWindow("opencamera", CV_WINDOW_AUTOSIZE);  
  73.     Mat frame;  
  74.     while (1)  
  75.     {  
  76.         capture >> frame;             //摄像头处抓取一帧  
  77.         imshow("opencamera", frame);  //显示  
  78.         //if (waitKey(30) == 27) break;   
  79.         waitKey(1);  
  80.   
  81.         cv::Mat yuvImg;  
  82.         cv::cvtColor(frame, yuvImg, CV_BGR2YUV_I420);    //YUV转RGB  
  83.         memcpy(yuv_buffer, yuvImg.data, yuv_bufLen*sizeof(unsigned char));    //YUV数据复制到yuv_buffer中  
  84.         //fwrite(yuv_buffer, yuv_bufLen*sizeof(unsigned char), 1, pFileOut);    //YUV写入本地  
  85.   
  86.         //while (fread(yuv_buffer, 1, yuv_size, inf) > 0)  
  87.         //{  
  88.         pic_in.i_pts = i_pts++;  
  89.         x264_encoder_encode(encoder, &nals, &nnal, &pic_in, &pic_out);      //编码一帧数据  
  90.         x264_nal_t *nal;  
  91.   
  92.         int j = 0;  
  93.         struct result_node_datatype *result_node = new struct result_node_datatype;  
  94.         result_node->result = new unsigned char[800000];  
  95.         memset(result_node->result, '\0', 800000);  
  96.         result_node->size = 0;  
  97.         for (nal = nals; nal < nals + nnal; nal++)  
  98.         {  
  99.             //fwrite(nal->p_payload, 1, nal->i_payload, fp_out);      //产生的NAL保存在本地  
  100.             //result_node->size += nal->i_payload;  
  101.             //memcpy(result_node->result, nal->p_payload, nal->i_payload);  
  102.             //cout << "nal->i_payload = " <<nal->i_payload<< endl;  
  103.             //j = j + nal->i_payload;  
  104.             //result_push(result_link, result_node);  
  105.             //cout << "in for(nal): j = "<<j << endl;  
  106.   
  107.             memcpy(result_node->result + j, nal->p_payload, nal->i_payload);  
  108.             j = j + nal->i_payload;  
  109.         }  
  110.         result_node->size = j;  
  111.         cout << "result_node->size = " << result_node->size << endl;  
  112.         result_push(result_link, result_node);  
  113.     }  
  114.   
  115.     x264_encoder_close(encoder);   //关闭编码器  
  116.     //fclose(inf);  
  117.     //free(yuv_buffer);  
  118.     //fclose(pFileOut);  
  119.     //delete[] pYuvBuf;  
  120.     //Sleep(100);  
  121. #endif  
  122.     return NULL;  
  123. }  

    X264编码的过程可以参考这里:点击打开链接

    需要注意的是,我们定义了一个为0的值j。编码产生后的NAL单元个数是nnal,编码后数据的起始地址是nal->p_payload,长度是nal->i_payload。增加j的原因是想把得到的一个个NAL单元累加在一起,组成一个完整帧的数据,最后一帧的长度就是j,然后将得到的一帧数据与长度送入队列,这是一个线程函数。对解码器来说,只有接收到完整的一帧,才能成功解码。


3.队列函数:

[cpp]  view plain  copy
  1. void result_push(result_link_type* result_link, result_node_datatype * result_node) //入队操作  
  2. {  
  3.     if (result_link->head == NULL)  
  4.     {  
  5.         result_link->head = result_node;  
  6.         result_link->end = result_link->head;  
  7.         result_link->result_num++;  
  8.     //  cout << "0: result_link->result_num++" << endl;  
  9.     }  
  10.     else  
  11.     {  
  12.         result_link->end->next = result_node;  
  13.         result_link->end = result_node;  
  14.         result_link->result_num++;  
  15.     //  cout << "1: result_link->result_num++" << endl;  
  16.     }  
  17. }  
  18.   
  19. struct result_node_datatype* result_pop(result_link_type* result_link) //出队操作  
  20. {  
  21.     struct result_node_datatype* tmp_node;  
  22.     if (result_link->head == NULL)  
  23.         return NULL;  
  24.     else if (result_link->head == result_link->end)  
  25.     {  
  26.         //  cout << "result_link->head == result_link->end " << endl;  
  27.         return NULL;  
  28.     }  
  29.     else  
  30.     {  
  31.         tmp_node = result_link->head;  
  32.         result_link->head = result_link->head->next;  
  33.         result_link->result_num--;  
  34.         //cout << "result_link->result_num--" << endl;  
  35.         return tmp_node;  
  36.     }  
  37. }  


4.解码过程:

    解码之前,要添加标志位0001。

[cpp]  view plain  copy
  1. bool get_h264_data(uchar* buf,int in_len,uchar* out_buf, int &out_len)  
  2. {  
  3.     char nalu[4] = { 0x00, 0x00, 0x00, 0x01 };  
  4.     memcpy(out_buf, nalu, 4);  
  5.     out_buf += 4;  
  6.     memcpy(out_buf, buf, in_len);  
  7.     out_len = in_len + 4;  
  8. //  cout << "out_len = " <<out_len<< endl;  
  9.     return true;  
  10. }  

    解码过程:

[cpp]  view plain  copy
  1. int main(int argc, char* argv[])  
  2. {  
  3.     HANDLE thread1;  
  4.     result_link_type *result_link = new result_link_type;  
  5.     result_link->head = result_link->end = NULL;  
  6.     result_link->result_num = 0;  
  7.     thread1 = CreateThread(NULL, 0, x264_encode, (LPVOID)result_link, 0, NULL);  
  8.     Sleep(1);  
  9.     //system("pause");  
  10. #if 1  
  11.     Mat pCvMat;  
  12.     AVCodec *pCodec;  
  13.     AVCodecContext *pCodecCtx = NULL;  
  14.     AVCodecParserContext *pCodecParserCtx = NULL;  
  15.   
  16.     int frame_count;  
  17.     FILE *fp_in;  
  18.     FILE *fp_out;  
  19.     AVFrame *pFrame, *pFrameYUV;  
  20.     uint8_t *out_buffer;  
  21. //  const int in_buffer_size = 4096;  
  22.     const int in_buffer_size = 800000;  
  23.     //uint8_t in_buffer[in_buffer_size + FF_INPUT_BUFFER_PADDING_SIZE] = { 0 };  
  24.     uint8_t in_buffer[in_buffer_size];  
  25.     memset(in_buffer, 0, sizeof(in_buffer));  
  26.     uint8_t *cur_ptr;  
  27.     int cur_size;  
  28.   
  29.     AVPacket packet;  
  30.     int ret, got_picture;  
  31.   
  32.     int y_size;  
  33.   
  34.     AVCodecID codec_id = AV_CODEC_ID_H264;  
  35. //  char filepath_in[] = "test.h264";  
  36.   
  37. //  char filepath_out[] = "1.yuv";  
  38.     int first_time = 1;  
  39.   
  40.     struct SwsContext *img_convert_ctx;  
  41.   
  42.     //av_log_set_level(AV_LOG_DEBUG);  
  43.   
  44.     avcodec_register_all();  
  45.   
  46.     pCodec = avcodec_find_decoder(codec_id);  
  47.     if (!pCodec) {  
  48.         printf("Codec not found\n");  
  49.         return -1;  
  50.     }  
  51.     pCodecCtx = avcodec_alloc_context3(pCodec);  
  52.     if (!pCodecCtx){  
  53.         printf("Could not allocate video codec context\n");  
  54.         return -1;  
  55.     }  
  56.   
  57.     pCodecParserCtx = av_parser_init(codec_id);  
  58.     if (!pCodecParserCtx){  
  59.         printf("Could not allocate video parser context\n");  
  60.         return -1;  
  61.     }  
  62.   
  63.     if (pCodec->capabilities&CODEC_CAP_TRUNCATED)  
  64.         pCodecCtx->flags |= CODEC_FLAG_TRUNCATED; /* we do not send complete frames */  
  65.   
  66.     if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {  
  67.         printf("Could not open codec\n");  
  68.         return -1;  
  69.     }  
  70. #if 0  
  71.     //Input File  
  72.     fp_in = fopen(filepath_in, "rb");  
  73.     if (!fp_in) {  
  74.         printf("Could not open input stream\n");  
  75.         return -1;  
  76.     }  
  77.     //Output File  
  78.     fp_out = fopen(filepath_out, "wb");  
  79.     if (!fp_out) {  
  80.         printf("Could not open output YUV file\n");  
  81.         return -1;  
  82.     }  
  83. #endif  
  84.     pFrame = av_frame_alloc();  
  85.     av_init_packet(&packet);  
  86.     AVFrame* pFrameBGR = av_frame_alloc(); //存储解码后转换的RGB数据    
  87.     // 保存BGR,opencv中是按BGR来保存的    
  88.     int size;  
  89.     //cout << "pCodecCtx->width = " << pCodecCtx->width << "\npCodecCtx->height = " << pCodecCtx->height << endl;  
  90.     //pCvMat.create(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);  
  91.     struct result_node_datatype *result_node2 = NULL;  
  92.   
  93.     int out_len;  
  94.   
  95.     while (1)   
  96.     {  
  97.     //  cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);  
  98.     //  cout << "result_link->size = " << result_link->result_num << endl;  
  99.         result_node2 = result_pop(result_link);  
  100.         if (result_node2 == NULL)  
  101.         {  
  102.             Sleep(1);  
  103.         //  cout << "result_node2 is NULL" << endl;  
  104.             continue;  
  105.         }  
  106.         //cur_size = result_node2->size;  
  107.         //cout<<"after result_pop()" << endl;  
  108.       
  109.         get_h264_data(result_node2->result, result_node2->size, in_buffer, out_len);  
  110.   
  111.         //cur_size = result_node2->size;  
  112.         cur_size = out_len;  
  113.         cout << "cur_size = " << cur_size << endl;  
  114.         if (cur_size == 0)  
  115.             break;  
  116.         cur_ptr = in_buffer;  
  117.         //cur_ptr = result_node2->result;  
  118.   
  119.         while (cur_size>0){  
  120.   
  121.             int len = av_parser_parse2(  
  122.                 pCodecParserCtx, pCodecCtx,  
  123.                 &packet.data, &packet.size,  
  124.                 cur_ptr, cur_size,  
  125.                 AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);  
  126.   
  127.             cur_ptr += len;  
  128.             cur_size -= len;  
  129.   
  130.             if (packet.size == 0)  
  131.                 continue;  
  132.   
  133.             //Some Info from AVCodecParserContext  
  134.             printf("Packet Size:%6d\t", packet.size);  
  135.             switch (pCodecParserCtx->pict_type){  
  136.             case AV_PICTURE_TYPE_I: printf("Type: I\t"); break;  
  137.             case AV_PICTURE_TYPE_P: printf("Type: P\t"); break;  
  138.             case AV_PICTURE_TYPE_B: printf("Type: B\t"); break;  
  139.             default: printf("Type: Other\t"); break;  
  140.             }  
  141.             printf("Output Number:%4d\t", pCodecParserCtx->output_picture_number);  
  142.             printf("Offset:%8ld\n", pCodecParserCtx->cur_offset);  
  143.   
  144.             ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);  
  145.             if (ret < 0) {  
  146.                 printf("Decode Error.(解码错误)\n");  
  147.                 return ret;  
  148.             }  
  149.             if (got_picture) {  
  150.                 if (first_time){  
  151.                     printf("\nCodec Full Name:%s\n", pCodecCtx->codec->long_name);  
  152.                     printf("width:%d\nheight:%d\n\n", pCodecCtx->width, pCodecCtx->height);  
  153.                     //SwsContext  
  154.                     //img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,  
  155.                     //  pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);  
  156.                     img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);  
  157.                     //pFrameYUV = av_frame_alloc();  
  158.                     //out_buffer = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));  
  159.                     //avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);  
  160.   
  161.                     //y_size = pCodecCtx->width*pCodecCtx->height;  
  162.                     //size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);  
  163.   
  164.                     size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);  
  165.                     out_buffer = (uint8_t *)av_malloc(size);  
  166.                     avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); // allocator memory for BGR buffer    
  167.                     cout << "pCodecCtx->width = " << pCodecCtx->width << "\npCodecCtx->height = " << pCodecCtx->height << endl;  
  168.                     pCvMat.create(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);  
  169.                     first_time = 0;  
  170.                 }  
  171.   
  172.                 printf("Succeed to decode 1 frame!\n");  
  173.                 //sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,pFrameYUV->data, pFrameYUV->linesize);  
  174.                 sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameBGR->data, pFrameBGR->linesize);  
  175.   
  176.                 //fwrite(pFrameYUV->data[0], 1, y_size, fp_out);     //Y   
  177.                 //fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_out);   //U  
  178.                 //fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_out);   //V  
  179.                 cout << "size = " << size << endl;  
  180.                 memcpy(pCvMat.data, out_buffer, size);  
  181.                 imshow("RGB", pCvMat);  
  182.                 waitKey(1);  
  183.   
  184.             }  
  185.         }  
  186.     }  
  187.   
  188.     system("pause");  
  189.     //Flush Decoder  
  190.     packet.data = NULL;  
  191.     packet.size = 0;  
  192.   
  193. #if 0  
  194.     while (1){  
  195.         ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);  
  196.         if (ret < 0) {  
  197.             printf("Decode Error.(解码错误)\n");  
  198.             return ret;  
  199.         }  
  200.         if (!got_picture)  
  201.             break;  
  202.         if (got_picture) {  
  203.             printf("Flush Decoder: Succeed to decode 1 frame!\n");  
  204.             sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,  
  205.                 pFrameYUV->data, pFrameYUV->linesize);  
  206.   
  207.             fwrite(pFrameYUV->data[0], 1, y_size, fp_out);     //Y  
  208.             fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_out);   //U  
  209.             fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_out);   //V  
  210.         }  
  211.     }  
  212. #endif  
  213.   
  214. //  fclose(fp_in);  
  215. //  fclose(fp_out);  
  216.   
  217.     sws_freeContext(img_convert_ctx);  
  218.     av_parser_close(pCodecParserCtx);  
  219.   
  220.     //av_frame_free(&pFrameYUV);  
  221.     av_frame_free(&pFrameBGR);  
  222.     av_frame_free(&pFrame);  
  223.     avcodec_close(pCodecCtx);  
  224.     av_free(pCodecCtx);  
  225. #endif  
  226.     return 0;  
  227. }  

    解码流程以及基本参数和函数的意义,可以参考链接:点击打开链接

    解码成功后,got_picture为非零值,在这个判断里面,解码出的数据转为RGB格式,并用opencv显示出来。


    运行效果图:

 

    运行的过程中,对比上面的效果图,发现解码后的图像比摄像头要延迟几秒钟,这是因为在编码的过程,我们使用了参数animation,编码开始前会缓存几帧,再开始,这样帧间编码效果好,压缩效率高。如果使用zerolatency(零延时),基本不会有延时效果,但是压缩效果不太好。

    

    完整测试项目的代码下载地址:点击打开链接

猜你喜欢

转载自blog.csdn.net/zwz1984/article/details/79566957