使用cocos2d-x + ffmpeg播放视频

1、未完成,待补充,完善后会上传完整代码(包含ffmpeg).目标平台暂定ios,完善后会完美跨平台。

2、实用价值跟遇到的困难不成正比,研究价值更大。

3、我们需要一个通用的,可嵌入到游戏内部的视频播放控件。 现有的解决方案都是android和iOS各自实用系统控件进行封装。好处是实现简单,一般情况下稳定,并且解码效率高。 缺点是无法与游戏真正契合在一起,毕竟是作为只能悬浮在游戏之上,并且iOS下面的MPPlayer还有一些恶心的问题,比如视频方向和大小莫名其妙的改变了。 

      另外,一个通用的播放控件可以完美的跨平台。比如现在的wp8版本暂时还无法播放视频,而且可以想见,如果使用的是native c++的系统结构,那么一定没有系统视频控件。

------------------------------------正文-----------------------------------

1、首先我们要做的是下载ffmpeg的代码 (git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg) ,在windows下编译ffmpeg是相当麻烦的一件事(原因是微软的vs死活不支持c99,而ffmpeg死活要用c99),这个最后再研究。iOS下面最简单,所以先研究iOS平台。 另外要注意,使用越新版本的ffmpeg,出问题的可能性越大。所以直接clone上面的地址不见得是一件正确的事情,直接下载一个稳定版本最好。

2、下载最新的gas-preprocessor并拷贝到/usr/bin目录下,这个是unix下用来预处理汇编文件的脚本,mac下没有,需要到libav的网站去下载(http://git.libav.org/?p=gas-preprocessor.git),如果版本与ffmpeg不一致的话,可能会在编译汇编文件的时候出现编译错误

unknown register alias 'TCOS_D0_HEAD'

      ps:小说明一下libav,这个是ffmpeg的一个fork分支,起因是领导者对如何维护ffmpeg项目的分歧。代码非常相似,并且ffmpeg会经常从libav merge代码。不过无所谓一个要优于另外一个,否则就不会依然存在两个项目了。

3、在命令行下面切换到ffmpeg代码目录,运行configure

编译armv7版本的静态库

[plain]  view plain copy
  1. ./configure --prefix=armv7 --disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-ffserver --enable-avresample --enable-cross-compile --sysroot="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk" --target-os=darwin --cc="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc" --extra-cflags="-arch armv7 -mfpu=neon -miphoneos-version-min=5.1" --extra-ldflags="-arch armv7 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk -miphoneos-version-min=4.3" --arch=arm --cpu=cortex-a9 --enable-pic  

待执行完毕后,运行 make; make install,如果权限不做就加上sudo。

注意几个参数,--prefix指定了make install的时候拷贝头文件和静态库的目标路径。  --sysroot指定了SDK版本,这个基本上一个xcode只有一个,要指定对,否则无法运行gcc。 --arch  --cpu 指定了当前cpu架构。  另外一些参数决定了要编译的内容。如果全部编译的话静态库有100+mb,很多编码器和解码器我们是用不到的,可以直接干掉。


同理,如果我们想要在模拟器下运行的话,还需要i386版本的静态库。

[plain]  view plain copy
  1. ./configure --prefix=i386 --disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-ffserver --enable-avresample --enable-cross-compile --sysroot="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.1.sdk" --target-os=darwin --cc="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc" --extra-cflags="-arch i386" --extra-ldflags="-arch i386 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.1.sdk" --arch=i386 --cpu=i386 --enable-pic --disable-asm  
4、当生成完毕两个版本的静态库后,我们需要使用lipo来合并两个版本,制作一个通用库。

[plain]  view plain copy
  1. lipo -output universal/lib/libavcodec.a  -create -arch armv7 armv7/lib/libavcodec.a -arch i386 i386/lib/libavcodec.a  
  2. lipo -output universal/lib/libavdevice.a  -create -arch armv7 armv7/lib/libavdevice.a -arch i386 i386/lib/libavdevice.a  
  3. lipo -output universal/lib/libavfilter.a  -create -arch armv7 armv7/lib/libavfilter.a -arch i386 i386/lib/libavfilter.a  
  4. lipo -output universal/lib/libavformat.a  -create -arch armv7 armv7/lib/libavformat.a -arch i386 i386/lib/libavformat.a  
  5. lipo -output universal/lib/libavresample.a  -create -arch armv7 armv7/lib/libavresample.a -arch i386 i386/lib/libavresample.a  
  6. lipo -output universal/lib/libavutil.a  -create -arch armv7 armv7/lib/libavutil.a -arch i386 i386/lib/libavutil.a  
  7. lipo -output universal/lib/libswresample.a  -create -arch armv7 armv7/lib/libswresample.a -arch i386 i386/lib/libswresample.a  
  8. lipo -output universal/lib/libswscale.a  -create -arch armv7 armv7/lib/libswscale.a -arch i386 i386/lib/libswscale.a  
如果熟悉shell脚本的话,可以把上面的这些操作写成一个shell脚本,这样就方便很多了。 这里直接贴出这些步骤,一个图方便,二是明了我们真正需要操作什么。

5、生成通用静态库后,就可以直接把这些库加入到xcode项目中,设置好头文件依赖。 这里需要注意的是,包含ffmpeg头文件时需要用extern "C" {} 来括起来,否则就是一大堆链接错误

6、写一个CCVideoLayer来用cocos2d-x播放视频

       头文件

[cpp]  view plain copy
  1. #pragma once  
  2. #include "cocos2d.h"  
  3.   
  4. #ifdef __APPLE__  
  5. #include   
  6. namespace std {  
  7.     namespace tr1 {}  
  8.     using namespace tr1;  
  9.     using tr1::function;  
  10. }  
  11. //using namespace std::tr1;  
  12. #else  
  13. #include   
  14. #endif  
  15.   
  16. struct AVFormatContext;  
  17. struct AVCodecContext;  
  18. struct AVFrame;  
  19. struct AVPicture;  
  20. struct SwsContext;  
  21.   
  22. NS_CC_BEGIN  
  23.   
  24. class CCVideoLayer : public CCSprite  
  25. {  
  26. public:  
  27.     static CCVideoLayer* create(const char* path, int width, int height);  
  28.     CCVideoLayer();  
  29.     virtual ~CCVideoLayer();  
  30.       
  31.     bool init(const char* path, int width, int height);  
  32.     void play(void);  
  33.     void stop(void);  
  34.     void pause(void);  
  35.     void seek(double sec);  
  36.     void draw(void);  
  37.     void update(float dt);  
  38.       
  39.     virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);  
  40.     virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);  
  41.     virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);  
  42.     virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);  
  43.       
  44.     void setEnableTouchEnd(bool enable);                        // 是否可以点击关闭视频  
  45.     void setVideoEndCallback(std::function<void()> func);       // 关闭视频回调  
  46. private:  
  47.     unsigned int m_width;  
  48.     unsigned int m_height;  
  49.       
  50.     AVFormatContext *pFormatCtx;  
  51.     AVCodecContext *pCodecCtx;  
  52.     AVFrame *pFrame;  
  53.     AVPicture* picture;  
  54.     int videoStream;                // 视频流  
  55.     int audioStream;                // 音频流  
  56.     SwsContext *img_convert_ctx;  
  57.       
  58.     std::string m_filePath;  
  59.     double m_frameRate;             // 帧率  
  60.     double m_elapsed;               // 用于帧率控制  
  61.   
  62.     bool m_enableTouchEnd;  
  63.     std::function<void()> m_videoEndCallback;  
  64. };  
  65.   
  66.   
  67. NS_CC_END  


实现文件

[cpp]  view plain copy
  1. //  
  2. //  CCVideoLayer.cpp  
  3. //  libquickcocos2dx  
  4. //  
  5. //  Created by langresser on 13-11-7.  
  6. //  Copyright (c) 2013年 qeeplay.com. All rights reserved.  
  7. //  
  8.   
  9. #include "CCVideoLayer.h"  
  10. #include "SimpleAudioEngine.h"  
  11.   
  12. #define kEnableFFMPEG 0  
  13.   
  14. #if kEnableFFMPEG  
  15. extern "C" {  
  16. #include "libavformat/avformat.h"  
  17. #include "libswscale/swscale.h"  
  18. }  
  19. #endif  
  20.   
  21. using namespace CocosDenshion;  
  22.   
  23. NS_CC_BEGIN  
  24. CCVideoLayer* CCVideoLayer::create(const char* path, int width, int height)  
  25. {  
  26.     CCVideoLayer* video = new CCVideoLayer();  
  27.     if (video) {  
  28.         video->init(path, width, height);  
  29.     }  
  30.     return video;  
  31. }  
  32.   
  33. CCVideoLayer::CCVideoLayer()  
  34. {  
  35. #if kEnableFFMPEG  
  36.     pFormatCtx = NULL;  
  37.     pCodecCtx = NULL;  
  38.     pFrame = NULL;  
  39.     picture = NULL;  
  40.     img_convert_ctx = NULL;  
  41. #endif  
  42.   
  43.     m_frameRate = 1 / 30.0;  
  44.     m_elapsed = 0;  
  45.     m_enableTouchEnd = false;  
  46. }  
  47.   
  48. CCVideoLayer::~CCVideoLayer()  
  49. {  
  50. #if kEnableFFMPEG  
  51.     sws_freeContext(img_convert_ctx);  
  52.       
  53.     // Free RGB picture  
  54.     avpicture_free(picture);  
  55.     delete picture;  
  56.       
  57.     // Free the YUV frame  
  58.     av_free(pFrame);  
  59.       
  60.     // Close the codec  
  61.     if (pCodecCtx) avcodec_close(pCodecCtx);  
  62.       
  63.     if (pFormatCtx) {  
  64.         avformat_close_input(&pFormatCtx);  
  65.     }  
  66. #endif  
  67. }  
  68.   
  69. bool CCVideoLayer::init(const char* path, int width, int height)  
  70. {  
  71. #if kEnableFFMPEG  
  72.     AVCodec         *pCodec;  
  73.     m_width = width;  
  74.     m_height = height;  
  75.       
  76.     // Register all formats and codecs  
  77.     av_register_all();  
  78.       
  79.     m_filePath = CCFileUtils::sharedFileUtils()->fullPathForFilename(path);  
  80.     if(avformat_open_input(&pFormatCtx, m_filePath.c_str(), NULL, NULL) != 0) {  
  81.          return false;  
  82.     }  
  83.       
  84.     // 获取流信息  
  85.     if(avformat_find_stream_info(pFormatCtx, NULL) < 0) {  
  86.         return false;  
  87.     }  
  88.       
  89.     // 查找视频流和音频流,由于不是做播放器,只取第一个流就可以了。视频格式为游戏服务  
  90.     videoStream = -1;  
  91.     audioStream = -1;  
  92.     for(int i=0; inb_streams; i++) {  
  93.         //if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO)  
  94.         if(videoStream == -1 && pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {  
  95.             videoStream=i;  
  96.         }  
  97.   
  98.         if (audioStream == -1 && pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO) {  
  99.             audioStream = i;  
  100.         }  
  101.   
  102.         if (videoStream != -1 && audioStream != -1) {  
  103.             break;  
  104.         }  
  105.     }  
  106.       
  107.     //  没有视频流,无法播放  
  108.     if(videoStream == -1) {  
  109.          return false;  
  110.     }  
  111.       
  112.     // Get a pointer to the codec context for the video stream  
  113.     pCodecCtx=pFormatCtx->streams[videoStream]->codec;  
  114.       
  115.     // 获取视频帧率  
  116.     AVRational rational = pFormatCtx->streams[videoStream]->r_frame_rate;  
  117.     m_frameRate = 1.0 * rational.den / rational.num;  
  118.       
  119.     m_frameRate = 1.0 / 31;  
  120.       
  121.     // Find the decoder for the video stream  
  122.     pCodec=avcodec_find_decoder(pCodecCtx->codec_id);  
  123.     if(pCodec==NULL) {  
  124.         return false;  
  125.     }  
  126.       
  127.     if(avcodec_open2(pCodecCtx, pCodec, NULL)) {  
  128.         return false;  
  129.     }  
  130.       
  131.     // Allocate video frame  
  132.     pFrame=avcodec_alloc_frame();  
  133.       
  134.     // scale  
  135.     sws_freeContext(img_convert_ctx);  
  136.       
  137.     // 用于渲染的一帧图片数据。注意其中的data是一个指针数组,我们取视频流用于渲染(一般是第0个流)  
  138.     picture = new AVPicture;  
  139.     avpicture_alloc(picture, PIX_FMT_RGB24, m_width, m_height);  
  140.       
  141.     // 用于缩放视频到实际需求大小  
  142.     static int sws_flags =  SWS_FAST_BILINEAR;  
  143.     img_convert_ctx = sws_getContext(pCodecCtx->width,  
  144.                                      pCodecCtx->height,  
  145.                                      pCodecCtx->pix_fmt,  
  146.                                      m_width,  
  147.                                      m_height,  
  148.                                      PIX_FMT_RGB24,  
  149.                                      sws_flags, NULL, NULL, NULL);  
  150.       
  151.     // 渲染的纹理  
  152.     CCTexture2D *texture = new CCTexture2D();  
  153.     texture->initWithData(picture->data[videoStream], picture->linesize[videoStream]*m_height, kCCTexture2DPixelFormat_RGB888, m_width, m_height, CCSize(m_width, m_height));  
  154.     initWithTexture(texture);  
  155.       
  156.     this->setContentSize(CCSize(m_width, m_height));  
  157.       
  158.       
  159.     SimpleAudioEngine::sharedEngine()->preloadBackgroundMusic(m_filePath.c_str());  
  160. #endif  
  161.     return true;  
  162. }  
  163.   
  164. void CCVideoLayer::play()  
  165. {  
  166.     std::string path = m_filePath.substr(0, m_filePath.rfind('.')) + ".m4a";  
  167.     SimpleAudioEngine::sharedEngine()->playBackgroundMusic(path.c_str());  
  168.     m_elapsed = 0;  
  169.     seek(0);  
  170.       
  171.     this->schedule(schedule_selector(CCVideoLayer::update), m_frameRate);  
  172. }  
  173.   
  174. void CCVideoLayer::stop(void)  
  175. {  
  176.     this->unscheduleAllSelectors();  
  177.     SimpleAudioEngine::sharedEngine()->stopBackgroundMusic();  
  178. }  
  179.   
  180. void CCVideoLayer::pause(void)  
  181. {  
  182.     SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();  
  183. }  
  184.   
  185. void CCVideoLayer::seek(double sec)  
  186. {  
  187. #if kEnableFFMPEG  
  188.     AVRational timeBase = pFormatCtx->streams[videoStream]->time_base;  
  189.     int64_t targetFrame = (int64_t)((double)timeBase.den / timeBase.num * sec);  
  190.     avformat_seek_file(pFormatCtx, videoStream, targetFrame, targetFrame, targetFrame, AVSEEK_FLAG_FRAME);  
  191.     avcodec_flush_buffers(pCodecCtx);  
  192. #endif  
  193. }  
  194.   
  195. void CCVideoLayer::update(float dt)  
  196. {  
  197. #if kEnableFFMPEG  
  198.     m_elapsed += dt;  
  199. //    if (m_elapsed < m_frameRate) {  
  200. //        return;  
  201. //    }  
  202.       
  203.     m_elapsed = 0;  
  204.     AVPacket packet;  
  205.     int frameFinished=0;  
  206.       
  207.     while(!frameFinished && av_read_frame(pFormatCtx, &packet)>=0) {  
  208.         // Is this a packet from the video stream?  
  209.         if(packet.stream_index==videoStream) {  
  210.             // Decode video frame  
  211.             avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);  
  212.         }  
  213.           
  214.         // Free the packet that was allocated by av_read_frame  
  215.         av_free_packet(&packet);  
  216.     }  
  217.       
  218.     sws_scale (img_convert_ctx, pFrame->data, pFrame->linesize,  
  219.                0, pCodecCtx->height,  
  220.                picture->data, picture->linesize);  
  221.       
  222.     if (frameFinished == 0) {  
  223.         this->stop();  
  224.         if (m_videoEndCallback) {  
  225.             m_videoEndCallback();  
  226.         }  
  227.           
  228.         this->removeFromParentAndCleanup(true);  
  229.     }  
  230. #endif  
  231. }  
  232.   
  233. void CCVideoLayer::draw(void)  
  234. {  
  235. #if kEnableFFMPEG  
  236.     CC_PROFILER_START_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");  
  237.       
  238.     CCAssert(!m_pobBatchNode, "If CCSprite is being rendered by CCSpriteBatchNode, CCSprite#draw SHOULD NOT be called");  
  239.       
  240.     CC_NODE_DRAW_SETUP();  
  241.       
  242.     ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst );  
  243.       
  244.     if (m_pobTexture != NULL)  
  245.     {  
  246.         ccGLBindTexture2D( m_pobTexture->getName() );  
  247.         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_width, m_height, 0, GL_RGB, GL_UNSIGNED_BYTE,picture->data[videoStream]);  
  248.         //glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, GL_RGB, GL_UNSIGNED_BYTE,picture->data[videoStream]);  
  249.     }  
  250.     else  
  251.     {  
  252.         ccGLBindTexture2D(0);  
  253.     }  
  254.       
  255.     //  
  256.     // Attributes  
  257.     //  
  258.       
  259.     ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );  
  260.       
  261. #define kQuadSize sizeof(m_sQuad.bl)  
  262.     long offset = (long)&m_sQuad;  
  263.       
  264.     // vertex  
  265.     int diff = offsetof( ccV3F_C4B_T2F, vertices);  
  266.     glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));  
  267.       
  268.     // texCoods  
  269.     diff = offsetof( ccV3F_C4B_T2F, texCoords);  
  270.     glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));  
  271.       
  272.     // color  
  273.     diff = offsetof( ccV3F_C4B_T2F, colors);  
  274.     glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));  
  275.       
  276.     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);  
  277.       
  278.     CHECK_GL_ERROR_DEBUG();  
  279.       
  280.     CC_INCREMENT_GL_DRAWS(1);  
  281.       
  282.     CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");  
  283. #endif  
  284. }  
  285.   
  286. bool CCVideoLayer::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)  
  287. {  
  288.     return true;  
  289. }  
  290.   
  291. void CCVideoLayer::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent)  
  292. {  
  293. }  
  294.   
  295. void CCVideoLayer::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent)  
  296. {  
  297.     if (m_enableTouchEnd) {  
  298.         this->stop();  
  299.           
  300.         if (m_videoEndCallback) {  
  301.             m_videoEndCallback();  
  302.         }  
  303.           
  304.         this->removeFromParentAndCleanup(true);  
  305.     }  
  306. }  
  307.   
  308. void CCVideoLayer::ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent)  
  309. {  
  310. }  
  311.   
  312. void CCVideoLayer::setEnableTouchEnd(bool enable)  
  313. {  
  314.     m_enableTouchEnd = enable;  
  315.     if (enable) {  
  316.         setEventMode(kEventModeNormal);  
  317.     }  
  318. }  
  319.   
  320. void CCVideoLayer::setVideoEndCallback(std::function<void(void)> func)  
  321. {  
  322.     m_videoEndCallback = func;  
  323. }  
  324.   
  325.   
  326. NS_CC_END  


相关说明:

      上面的这个CCVideoLayer只是初步实现了视频播放功能(ios下测试完毕),后续还有很多要完善的地方。不过最近新的项目要开启了,估计短时间内不会继续研究这个了。

       值得完善的地方:

       1、使用glSubTexImage来提交纹理更新,而不是使用glTexImage提交纹理,这个有助于效率提升(具体情况待测试)

       2、基本的帧率控制在update函数里面

       3、没有实现声音播放。声音解码和播放需要再研究一下。(我希望fmodex可以实现buffer声音的播放,要不然每个平台实现一套声音播放引擎太麻烦了)

       4、个人对这个还是比较满意的,可以向CCSprite一样随意的蹂躏,也可以直接放在lua里面作为一个对象来处理。不用担心一些系统控件的实现细节。效率也可以接受。

你可能感兴趣的:(使用cocos2d-x + ffmpeg播放视频)