• FFMPEG+SDL2.0流媒体开发3---简易MP4视频播放器,提取MP4的H264视频序列解码并且显示


    简介

    之前写了一遍提取MP4中的音视频并且解码,这一篇引入SDL2.0来显示解码后的视频序列 实现一个简易的 视频播放器。 

    我这里用的FFMPEG和SDL2.0都是最新版的 可能网上的资料不是很多,API接口也变了很多,不过大体的思路还是一样的。

    分析几个FFMPEG函数 

    在这之前我们分析几个代码中可能引起疑问的FFMPEG几个函数的源代码,我已经尽我的能力添加了注释,因为实在没有文档可能有的地方也不是很详尽  不过大体还是能看懂的

    av_image_alloc (分配图片缓冲区) 

    我们在FFMPEG中引用了此函数,下面列举的函数都是这个函数里所引用到的 我都 添加了注释  这里注意下面的

    1. pointers 参数是一个指针数组  实际上他在初始化完毕之后会被赋值成连续的内存序列 具体看源代码  
    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(设置系统调色板)

    //设置系统化调色板根据不同像素格式 

    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)

    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(填充行线宽)

    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文件的视频,并播放实现简易视频播放器

    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 ",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帧,丢失 ",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! ");  
    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! ");  
    127.             return 0;  
    128.         }  
    129.         //获取流媒体信息  
    130.         if (avformat_find_stream_info(pFormatCtx, NULL) < 0)  
    131.         {  
    132.             printf("获取流媒体信息失败! ");  
    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! ");  
    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! ");  
    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! ");  
    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 ");  
    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("查找编码器失败! ");  
    192.                 goto end;  
    193.             }  
    194.             if (avcodec_open2(pStream->codec, dec, nullptr) != 0)  
    195.             {  
    196.                 printf("打开编码器失败! ");  
    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.     }  

    代码运行界面

  • 相关阅读:
    selenium学习笔记——上传文件
    selenium学习笔记——利用cookie信息直接登录
    Java中的switch分支注意点
    Java中的包
    11月14日用ajax、PHP、session做购物车
    各种进位制转换
    11月13日上午ajax返回数据类型为JSON数据的处理
    11月13日上午省、市、区(县)三级联动
    11月10日下午 ajax做显示信息以后用ajax、Bootstrp做弹窗显示信息详情
    11月10日上午ajax基础知识、用ajax做登录页面、用ajax验证用户名是否可用、ajax动态调用数据库
  • 原文地址:https://www.cnblogs.com/ting5/p/5069397.html
Copyright © 2020-2023  润新知