- 浏览: 535253 次
-
文章分类
最新评论
使用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版本的静态库
./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版本的静态库。
./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-asm4、当生成完毕两个版本的静态库后,我们需要使用lipo来合并两个版本,制作一个通用库。
lipo -output universal/lib/libavcodec.a -create -arch armv7 armv7/lib/libavcodec.a -arch i386 i386/lib/libavcodec.a lipo -output universal/lib/libavdevice.a -create -arch armv7 armv7/lib/libavdevice.a -arch i386 i386/lib/libavdevice.a lipo -output universal/lib/libavfilter.a -create -arch armv7 armv7/lib/libavfilter.a -arch i386 i386/lib/libavfilter.a lipo -output universal/lib/libavformat.a -create -arch armv7 armv7/lib/libavformat.a -arch i386 i386/lib/libavformat.a lipo -output universal/lib/libavresample.a -create -arch armv7 armv7/lib/libavresample.a -arch i386 i386/lib/libavresample.a lipo -output universal/lib/libavutil.a -create -arch armv7 armv7/lib/libavutil.a -arch i386 i386/lib/libavutil.a lipo -output universal/lib/libswresample.a -create -arch armv7 armv7/lib/libswresample.a -arch i386 i386/lib/libswresample.a 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播放视频
头文件
#pragma once #include "cocos2d.h" #ifdef __APPLE__ #include <tr1/functional> namespace std { namespace tr1 {} using namespace tr1; using tr1::function; } //using namespace std::tr1; #else #include <functional> #endif struct AVFormatContext; struct AVCodecContext; struct AVFrame; struct AVPicture; struct SwsContext; NS_CC_BEGIN class CCVideoLayer : public CCSprite { public: static CCVideoLayer* create(const char* path, int width, int height); CCVideoLayer(); virtual ~CCVideoLayer(); bool init(const char* path, int width, int height); void play(void); void stop(void); void pause(void); void seek(double sec); void draw(void); void update(float dt); virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent); virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent); virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent); virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent); void setEnableTouchEnd(bool enable); // 是否可以点击关闭视频 void setVideoEndCallback(std::function<void()> func); // 关闭视频回调 private: unsigned int m_width; unsigned int m_height; AVFormatContext *pFormatCtx; AVCodecContext *pCodecCtx; AVFrame *pFrame; AVPicture* picture; int videoStream; // 视频流 int audioStream; // 音频流 SwsContext *img_convert_ctx; std::string m_filePath; double m_frameRate; // 帧率 double m_elapsed; // 用于帧率控制 bool m_enableTouchEnd; std::function<void()> m_videoEndCallback; }; NS_CC_END
实现文件
// // CCVideoLayer.cpp // libquickcocos2dx // // Created by langresser on 13-11-7. // Copyright (c) 2013年 qeeplay.com. All rights reserved. // #include "CCVideoLayer.h" #include "SimpleAudioEngine.h" #define kEnableFFMPEG 0 #if kEnableFFMPEG extern "C" { #include "libavformat/avformat.h" #include "libswscale/swscale.h" } #endif using namespace CocosDenshion; NS_CC_BEGIN CCVideoLayer* CCVideoLayer::create(const char* path, int width, int height) { CCVideoLayer* video = new CCVideoLayer(); if (video) { video->init(path, width, height); } return video; } CCVideoLayer::CCVideoLayer() { #if kEnableFFMPEG pFormatCtx = NULL; pCodecCtx = NULL; pFrame = NULL; picture = NULL; img_convert_ctx = NULL; #endif m_frameRate = 1 / 30.0; m_elapsed = 0; m_enableTouchEnd = false; } CCVideoLayer::~CCVideoLayer() { #if kEnableFFMPEG sws_freeContext(img_convert_ctx); // Free RGB picture avpicture_free(picture); delete picture; // Free the YUV frame av_free(pFrame); // Close the codec if (pCodecCtx) avcodec_close(pCodecCtx); if (pFormatCtx) { avformat_close_input(&pFormatCtx); } #endif } bool CCVideoLayer::init(const char* path, int width, int height) { #if kEnableFFMPEG AVCodec *pCodec; m_width = width; m_height = height; // Register all formats and codecs av_register_all(); m_filePath = CCFileUtils::sharedFileUtils()->fullPathForFilename(path); if(avformat_open_input(&pFormatCtx, m_filePath.c_str(), NULL, NULL) != 0) { return false; } // 获取流信息 if(avformat_find_stream_info(pFormatCtx, NULL) < 0) { return false; } // 查找视频流和音频流,由于不是做播放器,只取第一个流就可以了。视频格式为游戏服务 videoStream = -1; audioStream = -1; for(int i=0; i<pFormatCtx->nb_streams; i++) { //if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) if(videoStream == -1 && pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) { videoStream=i; } if (audioStream == -1 && pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO) { audioStream = i; } if (videoStream != -1 && audioStream != -1) { break; } } // 没有视频流,无法播放 if(videoStream == -1) { return false; } // Get a pointer to the codec context for the video stream pCodecCtx=pFormatCtx->streams[videoStream]->codec; // 获取视频帧率 AVRational rational = pFormatCtx->streams[videoStream]->r_frame_rate; m_frameRate = 1.0 * rational.den / rational.num; m_frameRate = 1.0 / 31; // Find the decoder for the video stream pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL) { return false; } if(avcodec_open2(pCodecCtx, pCodec, NULL)) { return false; } // Allocate video frame pFrame=avcodec_alloc_frame(); // scale sws_freeContext(img_convert_ctx); // 用于渲染的一帧图片数据。注意其中的data是一个指针数组,我们取视频流用于渲染(一般是第0个流) picture = new AVPicture; avpicture_alloc(picture, PIX_FMT_RGB24, m_width, m_height); // 用于缩放视频到实际需求大小 static int sws_flags = SWS_FAST_BILINEAR; img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, m_width, m_height, PIX_FMT_RGB24, sws_flags, NULL, NULL, NULL); // 渲染的纹理 CCTexture2D *texture = new CCTexture2D(); texture->initWithData(picture->data[videoStream], picture->linesize[videoStream]*m_height, kCCTexture2DPixelFormat_RGB888, m_width, m_height, CCSize(m_width, m_height)); initWithTexture(texture); this->setContentSize(CCSize(m_width, m_height)); SimpleAudioEngine::sharedEngine()->preloadBackgroundMusic(m_filePath.c_str()); #endif return true; } void CCVideoLayer::play() { std::string path = m_filePath.substr(0, m_filePath.rfind('.')) + ".m4a"; SimpleAudioEngine::sharedEngine()->playBackgroundMusic(path.c_str()); m_elapsed = 0; seek(0); this->schedule(schedule_selector(CCVideoLayer::update), m_frameRate); } void CCVideoLayer::stop(void) { this->unscheduleAllSelectors(); SimpleAudioEngine::sharedEngine()->stopBackgroundMusic(); } void CCVideoLayer::pause(void) { SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic(); } void CCVideoLayer::seek(double sec) { #if kEnableFFMPEG AVRational timeBase = pFormatCtx->streams[videoStream]->time_base; int64_t targetFrame = (int64_t)((double)timeBase.den / timeBase.num * sec); avformat_seek_file(pFormatCtx, videoStream, targetFrame, targetFrame, targetFrame, AVSEEK_FLAG_FRAME); avcodec_flush_buffers(pCodecCtx); #endif } void CCVideoLayer::update(float dt) { #if kEnableFFMPEG m_elapsed += dt; // if (m_elapsed < m_frameRate) { // return; // } m_elapsed = 0; AVPacket packet; int frameFinished=0; while(!frameFinished && av_read_frame(pFormatCtx, &packet)>=0) { // Is this a packet from the video stream? if(packet.stream_index==videoStream) { // Decode video frame avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); } // Free the packet that was allocated by av_read_frame av_free_packet(&packet); } sws_scale (img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, picture->data, picture->linesize); if (frameFinished == 0) { this->stop(); if (m_videoEndCallback) { m_videoEndCallback(); } this->removeFromParentAndCleanup(true); } #endif } void CCVideoLayer::draw(void) { #if kEnableFFMPEG CC_PROFILER_START_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw"); CCAssert(!m_pobBatchNode, "If CCSprite is being rendered by CCSpriteBatchNode, CCSprite#draw SHOULD NOT be called"); CC_NODE_DRAW_SETUP(); ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst ); if (m_pobTexture != NULL) { ccGLBindTexture2D( m_pobTexture->getName() ); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_width, m_height, 0, GL_RGB, GL_UNSIGNED_BYTE,picture->data[videoStream]); //glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, GL_RGB, GL_UNSIGNED_BYTE,picture->data[videoStream]); } else { ccGLBindTexture2D(0); } // // Attributes // ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex ); #define kQuadSize sizeof(m_sQuad.bl) long offset = (long)&m_sQuad; // vertex int diff = offsetof( ccV3F_C4B_T2F, vertices); glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff)); // texCoods diff = offsetof( ccV3F_C4B_T2F, texCoords); glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff)); // color diff = offsetof( ccV3F_C4B_T2F, colors); glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); CHECK_GL_ERROR_DEBUG(); CC_INCREMENT_GL_DRAWS(1); CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw"); #endif } bool CCVideoLayer::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) { return true; } void CCVideoLayer::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent) { } void CCVideoLayer::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent) { if (m_enableTouchEnd) { this->stop(); if (m_videoEndCallback) { m_videoEndCallback(); } this->removeFromParentAndCleanup(true); } } void CCVideoLayer::ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent) { } void CCVideoLayer::setEnableTouchEnd(bool enable) { m_enableTouchEnd = enable; if (enable) { setEventMode(kEventModeNormal); } } void CCVideoLayer::setVideoEndCallback(std::function<void(void)> func) { m_videoEndCallback = func; } NS_CC_END
相关说明:
上面的这个CCVideoLayer只是初步实现了视频播放功能(ios下测试完毕),后续还有很多要完善的地方。不过最近新的项目要开启了,估计短时间内不会继续研究这个了。
值得完善的地方:
1、使用glSubTexImage来提交纹理更新,而不是使用glTexImage提交纹理,这个有助于效率提升(具体情况待测试)
2、基本的帧率控制在update函数里面
3、没有实现声音播放。声音解码和播放需要再研究一下。(我希望fmodex可以实现buffer声音的播放,要不然每个平台实现一套声音播放引擎太麻烦了)
4、个人对这个还是比较满意的,可以向CCSprite一样随意的蹂躏,也可以直接放在lua里面作为一个对象来处理。不用担心一些系统控件的实现细节。效率也可以接受。
相关推荐
例如,我们可以使用C++的Socket API或者第三方库如Poco,它们提供了封装好的Socket操作接口,使得在cocos2d-x项目中使用Socket变得更加便捷。同时,cocos2d-x的网络模块(如`Network`类)虽然主要用于HTTP请求,但也...
3. Cocos2d-JS开发:这里强调的是使用Cocos2d-x框架结合JavaScript语言进行开发的过程。由于Cocos2d-x支持多种编程语言(包括C++、JavaScript和Lua),Cocos2d-JS意味着开发者可以采用JavaScript来构建游戏项目。 4...
本文将详细介绍如何在 Windows 环境下使用 Visual Studio 2010 配置 Cocos2d-x 游戏引擎。Cocos2d-x 是一个跨平台的游戏引擎,可以在多种平台上运行,包括 Windows、Mac OS X、iOS、Android 等。 一、下载和安装 ...
【cocos2d-x + socket】是一个关于使用游戏开发框架cocos2d-x与网络通信技术socket相结合的主题。cocos2d-x是一个开源的、跨平台的2D游戏引擎,广泛应用于iOS、Android、Windows等多平台的游戏开发。而socket是网络...
在Cocos2d-x + Lua开发游戏的过程中,有时需要接入iOS原生SDK,例如应用内购买、广告SDK或社交平台SDK等。这篇文章主要探讨如何在Cocos2d-x结合Lua的环境中实现与iOS原生SDK的对接。首先,我们要明确这里的iOS原生...
通过分析和理解这个项目,开发者可以学习到如何在cocos2d-x中使用lua编写游戏,掌握游戏场景的创建、对象的交互、事件处理、资源加载和音频播放等关键技能。此外,对于iOS开发者,还可以了解如何在Xcode环境中配置和...
在游戏开发领域,cocos2d-x是一个广泛使用的2D游戏引擎,它基于C++编写,支持多平台,包括iOS、Android以及桌面系统。而LuaJIT则是一款优化过的Lua虚拟机,它的执行效率极高,特别适合用作游戏脚本语言。当cocos2d-x...
资源名称:Cocos2d-x实战:JS卷——Cocos2d-JS开发内容简介:本书是介绍Cocos2d-x游戏编程和开发技术书籍,介绍了使用Cocos2d-JS中核心类、瓦片地图、物理引擎、音乐音效、数据持久化、网络通信、性能优化、多平台...
1. **场景(Scene)与层(Layer)管理**:Cocos2d-x使用场景和层的概念组织游戏画面。场景是一个完整的屏幕视图,而层则是场景中的一个可独立管理的部分,多个层可以组合成一个场景。 2. **精灵(Sprite)与动画**...
本书会介绍Cocos2d-x的音频API,包括音乐播放、音效处理以及音效资源的加载和管理。 此外,书中还会涉及用户输入处理、触摸事件、键盘事件的响应,以及如何实现游戏的交互功能。对于网络编程,Cocos2d-x提供了网络...
2. **C++核心**:3.8版本的cocos2d-x使用C++作为主要开发语言,保证了性能和灵活性,同时也通过tolua++和jsb提供Lua和JavaScript接口。 3. **强大的渲染引擎**:支持精灵、批处理、骨骼动画、物理引擎等多种图形...
同时,音效和音乐管理也是游戏体验的重要组成部分,书中会介绍如何使用Cocos2d-x的音频API来播放和控制音频资源。 在实际项目开发中,内存管理和性能优化是关键。本书可能会详细讲解Cocos2d-x中的内存管理机制,如...
Cocos2d-x是一个开源的游戏开发框架,广泛用于2D游戏、实时渲染应用程序和其他互动内容的制作。这个压缩包“cocos2d-x-3.1.zip”包含了Cocos2d-x框架的3.1版本,这是一个经典且相对旧的版本,可能对于那些寻找历史...
cocos2d-x 是一个开源的游戏开发框架,使用 C++ 语言编写,支持多平台发布,包括 iOS、Android、Windows、macOS、Linux 和 Web。cocos2d-x v3.16 是该框架的一个版本号,本文档主要介绍了该版本的安装流程以及环境...
Cocos2d-x是一个广泛使用的开源游戏开发框架,它基于C++,同时支持Lua和JavaScript等多种脚本语言,为开发者提供了高效、跨平台的游戏开发解决方案。在3.13.1版本中,Cocos2d-x对Spine动画引擎的集成进行了更新,这...
cocos2d-x是一个基于MIT许可证的开源游戏引擎,它以快速、简单且功能强大的特性闻名,允许开发者使用C++、Lua和JavaScript进行跨平台开发,支持包括iOS、Android、Windows Phone、Blackberry以及Tizen在内的多个平台...
本篇将详细讲解如何在Microsoft Visual Studio 2012(VS2012)环境下设置和使用cocos2d-x 3.5的项目模板。 首先,下载并安装cocos2d-x 3.5。这通常包括下载源代码包,解压后配置环境变量,确保能够通过命令行调用...
《使用cocos2d-x-2.0-2.0.4开发的简单跨平台益智类魔塔小游戏》 cocos2d-x是一个开源的游戏开发框架,它基于C++,支持多平台,包括iOS、Android、Windows以及Mac OS等。在本项目中,开发者利用cocos2d-x 2.0.4版本...
cocos2d-x是一款跨平台的2D游戏开发框架,使用C++语言编写,支持Windows、iOS、Android等多个操作系统。在本项目中,开发者使用了Visual Studio 2010作为集成开发环境(IDE)来编译和调试代码,尽管描述中提到也可以...
总的来说,cocos2d-x 2.2.2是一个功能完备、易于学习和使用的2D游戏开发框架。通过深入理解并掌握这个版本,开发者可以创建出运行在多种平台上的高质量游戏,享受到cocos2d-x带来的强大开发体验。无论是初学者还是...