FFMPEG+SDL2.0流媒體開發3---簡易MP4視頻播放器,提取MP4的H264視頻序列解碼而且顯示

簡介

以前寫了一遍提取MP4中的音視頻而且解碼,這一篇引入SDL2.0來顯示解碼後的視頻序列 實現一個簡易的 視頻播放器。 數組

我這裏用的FFMPEG和SDL2.0都是最新版的 可能網上的資料不是不少,API接口也變了不少,不過大致的思路仍是同樣的。ide

分析幾個FFMPEG函數 

在這以前咱們分析幾個代碼中可能引發疑問的FFMPEG幾個函數的源代碼,我已經盡個人能力添加了註釋,由於實在沒有文檔可能有的地方也不是很詳盡  不過大致仍是能看懂的函數

av_image_alloc (分配圖片緩衝區) 

咱們在FFMPEG中引用了此函數,下面列舉的函數都是這個函數裏所引用到的 我都 添加了註釋  這裏注意下面的工具

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. pointers 參數是一個指針數組  實際上他在初始化完畢以後會被賦值成連續的內存序列 具體看源代碼  

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. int av_image_alloc(uint8_t *pointers[4], int linesizes[4],  
  2.                    int w, int h, enum AVPixelFormat pix_fmt, int align)  
  3. {     
  4.      //獲取描述符  
  5.     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);    
  6.     //  
  7.     int i, ret;  
  8.     uint8_t *buf;  
  9.     //若是不存在描述符那麼返回錯誤  
  10.     if (!desc)  
  11.         return AVERROR(EINVAL);  
  12.      //檢測圖像寬度 高度  
  13.     if ((ret = av_image_check_size(w, h, 0, NULL)) < 0)  
  14.         return ret;  
  15.     //填充line sizes  
  16.     if ((ret = av_image_fill_linesizes(linesizes, pix_fmt, align>7 ? FFALIGN(w, 8) : w)) < 0)  
  17.         return ret;  
  18.        
  19.      //初始化0  
  20.     for (i = 0; i < 4; i++)  
  21.         linesizes[i] = FFALIGN(linesizes[i], align);  
  22.     //若是計算的緩衝區尺寸<0  
  23.     if ((ret = av_image_fill_pointers(pointers, pix_fmt, h, NULL, linesizes)) < 0)  
  24.         return ret;    
  25.     //若是失敗 從新分配buf  
  26.     buf = av_malloc(ret + align);  
  27.     if (!buf)  
  28.         return AVERROR(ENOMEM);     
  29.         //再次調用 分配連續緩衝區  賦值給 pointers  
  30.     if ((ret = av_image_fill_pointers(pointers, pix_fmt, h, buf, linesizes)) < 0) {   
  31.         //若是分配失敗那麼釋放 緩衝區  
  32.         av_free(buf);  
  33.         return ret;  
  34.     }    
  35.     //檢測像素描述符 AV_PIX_FMT_FLAG_PAL 或AV_PIX_FMT_FLAG_PSEUDOPAL   
  36.     //Pixel format has a palette in data[1], values are indexes in this palette.  
  37.     /** 
  38.         The pixel format is "pseudo-paletted". This means that FFmpeg treats it as 
  39.         * paletted internally, but the palette is generated by the decoder and is not 
  40.         * stored in the file. * 
  41.     */  
  42.     if (desc->flags & AV_PIX_FMT_FLAG_PAL || desc->flags & AV_PIX_FMT_FLAG_PSEUDOPAL)   
  43.          //設置系統調色板  
  44.         avpriv_set_systematic_pal2((uint32_t*)pointers[1], pix_fmt);  
  45.   
  46.     return ret;  
  47. }  

 

avpriv_set_systematic_pal2(設置系統調色板)

//設置系統化調色板根據不一樣像素格式 ui

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. int avpriv_set_systematic_pal2(uint32_t pal[256], enum AVPixelFormat pix_fmt)  
  2. {  
  3.     int i;  
  4.   
  5.     for (i = 0; i < 256; i++) {  
  6.         int r, g, b;  
  7.   
  8.         switch (pix_fmt) {  
  9.         case AV_PIX_FMT_RGB8:  
  10.             r = (i>>5    )*36;  
  11.             g = ((i>>2)&7)*36;  
  12.             b = (i&3     )*85;  
  13.             break;  
  14.         case AV_PIX_FMT_BGR8:  
  15.             b = (i>>6    )*85;  
  16.             g = ((i>>3)&7)*36;  
  17.             r = (i&7     )*36;  
  18.             break;  
  19.         case AV_PIX_FMT_RGB4_BYTE:  
  20.             r = (i>>3    )*255;  
  21.             g = ((i>>1)&3)*85;  
  22.             b = (i&1     )*255;  
  23.             break;  
  24.         case AV_PIX_FMT_BGR4_BYTE:  
  25.             b = (i>>3    )*255;  
  26.             g = ((i>>1)&3)*85;  
  27.             r = (i&1     )*255;  
  28.             break;  
  29.         case AV_PIX_FMT_GRAY8:  
  30.             r = b = g = i;  
  31.             break;  
  32.         default:  
  33.             return AVERROR(EINVAL);  
  34.         }  
  35.         pal[i] = b + (g << 8) + (r << 16) + (0xFFU << 24);  
  36.     }  
  37.   
  38.     return 0;  
  39. }  

 

av_image_fill_pointers(填充av_image_alloc傳遞的unsigned char** data和linesize)

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. //返回圖像所需的大小   
  2. //而且分配了連續緩衝區  將 data 拼接成一個內存連續的 序列  
  3. int av_image_fill_pointers(uint8_t *data[4], enum AVPixelFormat pix_fmt, int height,  
  4.                            uint8_t *ptr, const int linesizes[4])  
  5. {     
  6.     int i, total_size, size[4] = { 0 }, has_plane[4] = { 0 };  
  7.     //獲取描述符  
  8.     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);   
  9.     //清空指針數組  
  10.     memset(data  , 0, sizeof(data[0])*4);  
  11.    //若是不存在描述符 返回錯誤  
  12.     if (!desc || desc->flags & AV_PIX_FMT_FLAG_HWACCEL)  
  13.         return AVERROR(EINVAL);  
  14.     //data[0]初始化爲ptr  
  15.     data[0] = ptr;   
  16.     //若是每行的像素 大於INT類型最大值 -1024/高度 返回  
  17.     if (linesizes[0] > (INT_MAX - 1024) / height)  
  18.         return AVERROR(EINVAL);   
  19.     //初始化size[0]  
  20.     size[0] = linesizes[0] * height;  
  21.     //若是 描述符的標誌是AV_PIX_FMT_FLAG_PAL或者AV_PIX_FMT_FLAG_PSEUDOPAL 那麼代表調色板放在data[1]而且是 256 32位置   
  22.     if (desc->flags & AV_PIX_FMT_FLAG_PAL ||  
  23.         desc->flags & AV_PIX_FMT_FLAG_PSEUDOPAL)   
  24.     {  
  25.         size[0] = (size[0] + 3) & ~3;  
  26.         data[1] = ptr + size[0];   
  27.         return size[0] + 256 * 4;  
  28.     }  
  29.      /** 
  30.      * Parameters that describe how pixels are packed. 
  31.      * If the format has 2 or 4 components, then alpha is last. 
  32.      * If the format has 1 or 2 components, then luma is 0. 
  33.      * If the format has 3 or 4 components, 
  34.      * if the RGB flag is set then 0 is red, 1 is green and 2 is blue; 
  35.      * otherwise 0 is luma, 1 is chroma-U and 2 is chroma-V.  
  36.  
  37.      */  
  38.     for (i = 0; i < 4; i++)  
  39.         has_plane[desc->comp[i].plane] = 1;  
  40.     //下面是計算總的須要的緩衝區大小  
  41.     total_size = size[0];  
  42.     for (i = 1; i < 4 && has_plane[i]; i++) {  
  43.         int h, s = (i == 1 || i == 2) ? desc->log2_chroma_h : 0;  
  44.         data[i] = data[i-1] + size[i-1];  
  45.         h = (height + (1 << s) - 1) >> s;  
  46.         if (linesizes[i] > INT_MAX / h)  
  47.             return AVERROR(EINVAL);  
  48.         size[i] = h * linesizes[i];  
  49.         if (total_size > INT_MAX - size[i])    
  50.             return AVERROR(EINVAL);  
  51.         total_size += size[i];  
  52.     }  
  53.    //返回總的緩衝區 大小  
  54.     return total_size;  
  55. }  

 

av_image_fill_linesizes(填充行線寬)

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. //填充LineSize數組 ,linesize表明每一剛的線寬 像素爲單位  
  2. int av_image_fill_linesizes(int linesizes[4], enum AVPixelFormat pix_fmt, int width)  
  3. {  
  4.     int i, ret;  
  5.     //獲取格式描述符  
  6.     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);  
  7.     int max_step     [4];       /* max pixel step for each plane */  
  8.     int max_step_comp[4];       /* the component for each plane which has the max pixel step */  
  9.     //初始化指針數組 0  
  10.     memset(linesizes, 0, 4*sizeof(linesizes[0]));  
  11.     //若是不存在那麼返回錯誤  
  12.     if (!desc || desc->flags & AV_PIX_FMT_FLAG_HWACCEL)  
  13.         return AVERROR(EINVAL);  
  14.     //下面的代碼都是填充線寬的代碼   
  15.     av_image_fill_max_pixsteps(max_step, max_step_comp, desc);  
  16.     for (i = 0; i < 4; i++) {   
  17.         if ((ret = image_get_linesize(width, i, max_step[i], max_step_comp[i], desc)) < 0)  
  18.             return ret;  
  19.         linesizes[i] = ret;  
  20.     }  
  21.   
  22.     return 0;  
  23. }  

 

例子 提取MP4文件的視頻,並播放實現簡易視頻播放器

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到個人代碼片
 
  1. #include "stdafx.h"  
  2. /************************************************************************/  
  3. /* 利用分流器分流MP4文件音視頻並進行解碼輸出   
  4. Programmer小衛-USher 2014/12/17 
  5. /************************************************************************/  
  6. //打開  
  7. #define __STDC_FORMAT_MACROS  
  8. #ifdef _CPPRTTI   
  9. extern "C"  
  10. {  
  11. #endif  
  12. #include "libavutil/imgutils.h"    //圖像工具   
  13. #include "libavutil/samplefmt.h"  // 音頻樣本格式  
  14. #include "libavutil/timestamp.h"  //時間戳工具能夠 被用於調試和日誌目的   
  15. #include "libavformat/avformat.h" //Main libavformat public API header  包含了libavf I/O和   Demuxing  和Muxing 庫     
  16. #include "SDL.h"  
  17. #ifdef _CPPRTTI   
  18. };  
  19. #endif  
  20.   
  21. //音視頻編碼器上下文  
  22. static AVCodecContext *pVideoContext,*pAudioContext;  
  23. static FILE *fVideoFile,*fAudioFile;  //輸出文件句柄  
  24. static AVStream *pStreamVideo,*pStreamAudio; //媒體流    
  25. static unsigned char * videoDstData[4];  //視頻數據   
  26. static int videoLineSize[4]; //   
  27. static int videoBufferSize; //視頻緩衝區大小   
  28. static AVFormatContext *pFormatCtx=NULL; //格式上下文  
  29. static AVFrame*pFrame=NULL ; //  
  30. static AVPacket pkt;  //解碼媒體包  
  31. static int ret=0; //狀態  
  32. static int gotFrame; //獲取到的視頻流  
  33. //音視頻流的索引  
  34. static int videoStreamIndex,audioStreamIndex;  
  35. //解碼媒體包  
  36. //SDL定義    
  37. SDL_Window * pWindow = NULL;  
  38. SDL_Renderer *pRender = NULL;  
  39. SDL_Texture *pTexture = NULL;  
  40. SDL_Rect dstrect = {0,0,800,600};  
  41. int frame = 0;  
  42. int indexFrameVideo=0;  
  43. static int decode_packet(int* gotFrame, int param2)  
  44. {  
  45.     int ret  = 0 ;  
  46.     //解碼數據大小  
  47.     int decodedSize=pkt.size ;   
  48.     //初始化獲取的數據幀爲0  
  49.     *gotFrame=0;  
  50.     //若是是視頻流那麼 解包視頻流    
  51.     if(pkt.stream_index==videoStreamIndex)  
  52.     {    
  53.   
  54.         //解碼數據到視頻幀  
  55.         if((ret=avcodec_decode_video2(pVideoContext,pFrame,gotFrame,&pkt))<0)  
  56.         {    
  57.             //解碼視頻幀失敗  
  58.             return ret ;  
  59.         }  
  60.         indexFrameVideo++;        
  61.         //copy 解壓後的數據到咱們分配的空間中  
  62.         if(*gotFrame)  
  63.         {  
  64.             //拷貝數據  
  65.             av_image_copy(videoDstData,videoLineSize, (const uint8_t **)(pFrame->data), pFrame->linesize,pVideoContext->pix_fmt, pVideoContext->width, pVideoContext->height);  
  66.             //寫入數據到緩衝區  
  67.             //fwrite(videoDstData[0], 1, videoBufferSize, fVideoFile);  
  68.             printf("輸出當前第%d幀,大小:%d\n",indexFrameVideo,videoBufferSize);   
  69.         int n = SDL_BYTESPERPIXEL(pStreamVideo->codec->pix_fmt);  
  70.           
  71.         //更新紋理  
  72.   
  73.         SDL_UpdateTexture(pTexture, &dstrect, (const void*)videoDstData[0], videoLineSize[0]);  
  74.   
  75.         //拷貝紋理到2D模塊  
  76.         SDL_RenderCopy(pRender, pTexture,NULL, &dstrect);  
  77.         //延時 1000ms*1/25  
  78.         SDL_Delay(1000 * 1 / frame);  
  79.         //顯示Render渲染曾  
  80.         SDL_RenderPresent(pRender);  
  81.         }else  
  82.         {  
  83.             printf("第%d幀,丟失\n",indexFrameVideo);  
  84.         }  
  85.     }  
  86.     //音頻無論  
  87.     else if(pkt.stream_index==audioStreamIndex)  
  88.     {    
  89.         ///解碼音頻信息  
  90. //      if ((ret = avcodec_decode_audio4(pAudioContext, pFrame, gotFrame, &pkt)) < 0)  
  91. //          return ret;  
  92. //      decodedSize = FFMIN(ret, pkt.size);  
  93. //      //算出當前幀的大小  
  94. //      size_t unpadded_linesize = pFrame->nb_samples * av_get_bytes_per_sample((AVSampleFormat)pFrame->format);   
  95. //      ///寫入數據到音頻文件  
  96. //      fwrite(pFrame->extended_data[0], 1, unpadded_linesize, fAudioFile);     
  97.     }   
  98.     //取消全部引用  而且重置frame字段  
  99.     av_frame_unref(pFrame);  
  100.     return decodedSize ;  
  101. }  
  102.   
  103.     int Demuxing(int argc, char** argv)  
  104.     {  
  105.         if (argc < 4)  
  106.         {  
  107.             printf("Parameter Error!\n");  
  108.             return 0;  
  109.         }  
  110.   
  111.         //註冊全部混流器 過濾器  
  112.         av_register_all();  
  113.         //註冊全部編碼器  
  114.         avcodec_register_all();  
  115.         //媒體輸入源頭  
  116.         char*pInputFile = argv[1];  
  117.         //視頻輸出文件  
  118.         char*pOutputVideoFile = argv[3];  
  119.         //音頻輸出文件  
  120.         char*pOutputAudioFile = argv[2];  
  121.         //分配環境上下文  
  122.         pFormatCtx = avformat_alloc_context();  
  123.         //打開輸入源  而且讀取輸入源的頭部  
  124.         if (avformat_open_input(&pFormatCtx, pInputFile, NULL, NULL) < 0)  
  125.         {  
  126.             printf("Open Input Error!\n");  
  127.             return 0;  
  128.         }  
  129.         //獲取流媒體信息  
  130.         if (avformat_find_stream_info(pFormatCtx, NULL) < 0)  
  131.         {  
  132.             printf("獲取流媒體信息失敗!\n");  
  133.             return 0;  
  134.         }  
  135.         //打印媒體信息  
  136.         av_dump_format(pFormatCtx, 0, pInputFile, 0);  
  137.         for (unsigned i = 0; i < pFormatCtx->nb_streams; i++)  
  138.         {  
  139.             AVStream *pStream = pFormatCtx->streams[i];  
  140.             AVMediaType mediaType = pStream->codec->codec_type;  
  141.             //提取不一樣的編解碼器  
  142.             if (mediaType == AVMEDIA_TYPE_VIDEO)  
  143.             {  
  144.                 videoStreamIndex = i;  
  145.                 pVideoContext = pStream->codec;  
  146.                 pStreamVideo = pStream;  
  147.                 fVideoFile = fopen(pOutputVideoFile, "wb");   
  148.                 frame = pVideoContext->framerate.num;  
  149.                 if (!fVideoFile)  
  150.                 {  
  151.                     printf("con't open file!\n");  
  152.                     goto end;  
  153.                 }  
  154.                 //計算解碼後一幀圖像的大小  
  155.                 //int nsize = avpicture_get_size(PIX_FMT_YUV420P, 1280, 720);  
  156.                 //分配計算初始化 圖像緩衝區 調色板數據  
  157.                 int ret = av_image_alloc(videoDstData, videoLineSize, pVideoContext->width, pVideoContext->height, pVideoContext->pix_fmt, 1);  
  158.                 if (ret < 0)  
  159.                 {  
  160.                     printf("Alloc video buffer error!\n");  
  161.                     goto end;  
  162.                 }  
  163.                 //avpicture_fill((AVPicture *)pFrame, videoDstData[0], PIX_FMT_YUV420P, 1280, 720);  
  164.                 videoBufferSize = ret;  
  165.             }  
  166.             else if (mediaType == AVMEDIA_TYPE_AUDIO)  
  167.             {  
  168.                 audioStreamIndex = i;  
  169.                 pAudioContext = pStream->codec;  
  170.                 pStreamAudio = pStream;  
  171.                 fAudioFile = fopen(pOutputAudioFile, "wb");  
  172.                 if (!fAudioFile)  
  173.                 {  
  174.                     printf("con't open file!\n");  
  175.                     goto end;  
  176.                 }  
  177.                 //分配視頻幀  
  178.                 pFrame = av_frame_alloc();  
  179.                 if (pFrame == NULL)  
  180.                 {  
  181.                     av_freep(&videoDstData[0]);  
  182.                     printf("alloc audio frame error\n");  
  183.                     goto end;  
  184.                 }  
  185.             }  
  186.             AVCodec *dec;  
  187.             //根據編碼器id查找編碼器  
  188.             dec = avcodec_find_decoder(pStream->codec->codec_id);  
  189.             if (dec == NULL)  
  190.             {  
  191.                 printf("查找編碼器失敗!\n");  
  192.                 goto end;  
  193.             }  
  194.             if (avcodec_open2(pStream->codec, dec, nullptr) != 0)  
  195.             {  
  196.                 printf("打開編碼器失敗!\n");  
  197.                 goto end;  
  198.             }  
  199.   
  200.         }  
  201.         av_init_packet(&pkt);  
  202.         pkt.data = NULL;  
  203.         pkt.size = 0;  
  204.   
  205.         //讀取媒體數據包  數據要大於等於0  
  206.         while (av_read_frame(pFormatCtx, &pkt) >= 0)  
  207.         {  
  208.             AVPacket oriPkt = pkt;  
  209.             do  
  210.             {  
  211.                 //返回每一個包解碼的數據  
  212.                 ret = decode_packet(&gotFrame, 0);  
  213.                 if (ret < 0)  
  214.                     break;  
  215.                 //指針後移  空閒內存減小  
  216.                 pkt.data += ret;  
  217.                 pkt.size -= ret;  
  218.                 //  
  219.             } while (pkt.size > 0);  
  220.             //釋放以前分配的空間  讀取完畢必須釋放包  
  221.             av_free_packet(&oriPkt);  
  222.         }  
  223.   
  224.     end:  
  225.         //關閉視頻編碼器  
  226.         avcodec_close(pVideoContext);  
  227.         //關閉音頻編碼器  
  228.         avcodec_close(pAudioContext);  
  229.         avformat_close_input(&pFormatCtx);  
  230.         fclose(fVideoFile);  
  231.         fclose(fAudioFile);  
  232.         //釋放編碼幀  
  233.         avcodec_free_frame(&pFrame);  
  234.         //釋放視頻數據區  
  235.         av_free(videoDstData[0]);    
  236.         return 0;  
  237.     }  
  238.   
  239.     int _tmain(int argc, char*argv[])  
  240.     {    
  241.         SDL_Init(SDL_INIT_VIDEO);    
  242.         //建立窗口  
  243.         pWindow = SDL_CreateWindow("YUV420P", 200, 100, 800, 600, 0);  
  244.         //啓用硬件加速   
  245.         pRender=SDL_CreateRenderer(pWindow, -1, 0);    
  246.         dstrect.x = 0;  
  247.         dstrect.y = 0;  
  248.         dstrect.w = 1280;  
  249.         dstrect.h = 720;  
  250.         //建立一個紋理  設置能夠Lock  YUV420P 格式 1280*720   
  251.         pTexture = SDL_CreateTexture(pRender, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, 1280, 720);  
  252.         Demuxing(argc, argv);     
  253.         //釋放  
  254.         SDL_RenderClear(pRender);  
  255.         SDL_DestroyTexture(pTexture);  
  256.         SDL_DestroyRenderer(pRender);  
  257.         SDL_DestroyWindow(pWindow);  
  258.         SDL_Quit();  
  259.         return  0;  
  260.     }  

 

 

代碼運行界面

相關文章
相關標籤/搜索