cocos2dx跨平台直播实例-ffmpeg-ios篇

一、环境

    mac 10.12.2

    cocos2dx-3.13.1

    ffmpeg 3.0

二、新建项目和编译库

    cocos2dx按照官网新建一个实例。

    ffmpeg编译ios库http://blog.csdn.net/u013654125/article/details/73549132

    ffmpeg编译完后,会有得到一个FFmpeg-iOS文件夹,文件夹里有include和lib这两个文件夹。这两个文件夹里就是ffmpeg编译好的头文件和库文件。

三、项目配置

cocos2dx跨平台直播实例-ffmpeg-ios篇_第1张图片

如上图:

这三个地方需要做一些修改。

第一个方框的配置

LiveVideo.cpp

//
//  LiveVideo.cpp
//  Game
//
//  Created by zhufu on 2017/3/1.
//
//

#include "LiveVideo.h"
#include "HelloWorldScene.h"

#ifndef INT64_C
#define INT64_C(c) (c ## LL)
#define UINT64_C(c) (c ## ULL)
#endif

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/avutil.h"
#include "libavfilter/avfilter.h"
#endif
}

Scene* LiveVideo::createScene()
{
    auto scene = Scene::create();
    auto sprite = Sprite::create("HelloWorld.png");
    
    
    // position the sprite on the center of the screen
    sprite->setPosition(Vec2(Director::getInstance()->getVisibleSize().width/2-140, Director::getInstance()->getVisibleSize().height/2+100));
    
    // add the sprite as a child to this layer
    scene->addChild(sprite, 0);
    LiveVideo* liveVideo = LiveVideo::create();
    scene->addChild(liveVideo);
    liveVideo->init();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}

LiveVideo* LiveVideo::create()
{
    LiveVideo* liveVideo = new(std::nothrow) LiveVideo();
    if(liveVideo)
    {
        liveVideo->autorelease();
        return liveVideo;
    }
    CC_SAFE_DELETE(liveVideo);
    return nullptr;
}

bool LiveVideo::init()
{
    initEvents();
    initCommand();
    //ffmpeg解码线程
    play();
    
    addPlayButton();
    addPlayHKSButton();
    addStopButton();
    addRefreshButton();
    
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    loadShader();
    loadTexture();
    loadRectangle();
    return true;
}

void LiveVideo::addPlayButton()
{
    auto button = ui::Button::create("CloseNormal.png", "CloseSelected.png");
    button->setTitleText("play");
    button->setPosition(Vec2(170, 100));
    button->addClickEventListener(CC_CALLBACK_0(LiveVideo::play, this));
    button->setContentSize(Size(100, 100));
    addChild(button);
}

void LiveVideo::addPlayHKSButton()
{
    auto button = ui::Button::create("CloseNormal.png", "CloseSelected.png");
    button->setTitleText("playHKS");
    button->setPosition(Vec2(240, 100));
    button->addClickEventListener(CC_CALLBACK_0(LiveVideo::playHKS, this));
    button->setContentSize(Size(100, 100));
    addChild(button);
}

void LiveVideo::addStopButton()
{
    auto button = ui::Button::create("CloseNormal.png", "CloseSelected.png");
    button->setTitleText("stop");
    button->setPosition(Vec2(170, 50));
    button->addClickEventListener(CC_CALLBACK_0(LiveVideo::stop, this));
    button->setContentSize(Size(100, 100));
    addChild(button);
}

void LiveVideo::addRefreshButton()
{
    auto button = ui::Button::create("CloseNormal.png", "CloseSelected.png");
    button->setTitleText("refresh");
    button->setPosition(Vec2(240, 50));
    button->addClickEventListener(CC_CALLBACK_0(LiveVideo::refresh, this));
    button->setContentSize(Size(100, 100));
    addChild(button);
}

void LiveVideo::initEvents()
{
    EventListenerCustom* stopDecodeListener = EventListenerCustom::create("stopFFmpegDecode", CC_CALLBACK_0(LiveVideo::stop, this));
    Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(stopDecodeListener, 1);
    
    EventListenerCustom* startDecodeListener = EventListenerCustom::create("startFFmpegDecode", CC_CALLBACK_0(LiveVideo::ffmpegDecode, this, currentLivePath));
    Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(startDecodeListener, 1);
}

void LiveVideo::play()
{
    if(!isPlay())
    {
        const char* filePath = "rtmp://113.10.194.251/live/mtable1";
        setPlay(true);
        std::thread t(&LiveVideo::ffmpegDecode, this, filePath);
        t.detach();
        
    }
    
}

void LiveVideo::playHKS()
{
    if(!isPlay())
    {
        const char* filePath ="rtmp://live.hkstv.hk.lxdns.com/live/hks";
        setPlay(true);
        std::thread t(&LiveVideo::ffmpegDecode, this, filePath);
        t.detach();
        
    }
    
}

void LiveVideo::stop()
{
    setPlay(false);
}

void LiveVideo::refresh()
{
    if(_playFlag)
    {
        clearBuf();
    } else {
        play();
    }
    
}

void LiveVideo::setPlay(bool playFlag)
{
    _playFlag = playFlag;
}

bool LiveVideo::isPlay()
{
    return _playFlag;
}

long LiveVideo::getCurrentTime()
{
    struct timeval now;
    gettimeofday(&now, NULL);
    return now.tv_sec*1000 +  (int)(now.tv_usec/1000+0.5);
}

void LiveVideo::flipVertical(int width, int height, char* arr)
{
    int index = 0, f_index, cycle=height>>1;
    char buf;
    for (int i = 0; i < cycle; i++)
    {
        for (int j = 0; j < width; j++)
        {
            //当前像素
            index = i*width + j;
            //需要交换的像素
            f_index = (height - 1 - i)*width + j;
            //缓存当前像素
            buf = arr[index];
            //交换像素
            arr[index] = arr[f_index];
            //交换回像素
            arr[f_index] = buf;
        }
    }
}

int LiveVideo::ffmpegDecode(const char* filePath)
{
    //开始解码
    CCLOG("filePath %s", filePath);
//    strcpy(currentLivePath, filePath);
    av_register_all();
    avformat_network_init();
    AVFormatContext* pFormat = NULL;
    AVCodecContext* video_dec_ctx = NULL;
    AVCodec* video_dec = NULL;
    AVPacket *pkt = NULL;
    AVFrame *pFrame = NULL;
    
    do {
        
        if (avformat_open_input(&pFormat, filePath, NULL, NULL) < 0)
        {
            CCLOG("Couldn't open input stream.(无法打开输入流)\n");
            break;
        }
        
        CCLOG("%lld", pFormat->probesize);
        CCLOG("%lld", pFormat->max_analyze_duration);
        pFormat->probesize = 100;
        pFormat->max_analyze_duration = 0;
        if (avformat_find_stream_info(pFormat, NULL) < 0)
        {
            CCLOG("Couldn't find stream information.(无法获取流信息)\n");
            break;
        }
        
        int i, videoIndex=-1;
        for(i = 0; inb_streams; i++)
        {
            if(pFormat->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
            {
                videoIndex=i;
                break;
            }
        }
        if(videoIndex==-1)
        {
            printf("Didn't find a video stream.(没有找到视频流)\n");
            break;
        }
        video_dec_ctx=pFormat->streams[videoIndex]->codec;
        _frameRate = pFormat->streams[videoIndex]->r_frame_rate.num;
        if(_frameRate == 0)
        {
            _frameRate = 1;
        }
        
        _system_start_time = getCurrentTime();
        _start_time = pFormat->streams[videoIndex]->start_time;
        
        _pixel_w = video_dec_ctx->width;
        _pixel_h = video_dec_ctx->height;
        int pixel_w2 = _pixel_w >> 1;
        int pixel_h2 = _pixel_h >> 1;
        video_dec=avcodec_find_decoder(video_dec_ctx->codec_id);
        if(video_dec==NULL)
        {
            printf("Codec not found.(没有找到解码器)\n");
            break;
        }
        if(avcodec_open2(video_dec_ctx, video_dec,NULL)<0)
        {
            printf("Could not open codec.(无法打开解码器)\n");
            break;
        }
        
        
        pkt = av_packet_alloc();
        av_init_packet(pkt);
        pFrame = av_frame_alloc();
    
    
        while (_playFlag)
        {
            if(av_read_frame(pFormat, pkt) < 0)
            {
                CCLOG("读取帧失败!!!!");
                break;
            }
            if (pkt->stream_index == videoIndex)
            {
                int got_picture = 0,ret = 0;
                ret = avcodec_decode_video2(video_dec_ctx, pFrame, &got_picture, pkt);
                if (ret < 0)
                {
                    printf("Decode Error.(解码错误)\n");
                    break;
                }
                if (got_picture)
                {
                    char* buf = new char[video_dec_ctx->height * video_dec_ctx->width * 3 / 2];
                    int a = 0, i;
                    for (i = 0; i<_pixel_h; i++)
                    {
                        memcpy(buf + a, pFrame->data[0] + i * pFrame->linesize[0], _pixel_w);
                        a += _pixel_w;
                    }
                    flipVertical(_pixel_w, _pixel_h, buf);
                    for (i = 0; idata[1] + i * pFrame->linesize[1], pixel_w2);
                        a += pixel_w2;
                    }
                    flipVertical(pixel_w2, pixel_h2, buf+_pixel_w*_pixel_h);
                    for (i = 0; idata[2] + i * pFrame->linesize[2], pixel_w2);
                        a += pixel_w2;
                    }
                    flipVertical(pixel_w2, pixel_h2, buf+_pixel_w*_pixel_h+_pixel_w*_pixel_h/4);
                    FrameData data;
                    data.pts = pFrame->pkt_pts;
                    data.buf = buf;
                    
                    _data.push_back(data);
                    buf = NULL;
                    
                    CCLOG("pts %lld", pkt->pts);
                    CCLOG("pts %lld", pFrame->pts);
                    AVRational test;
                    test = av_get_time_base_q();
                    CCLOG("test %d, %d", test.num, test.den);
                }
            }
            
            av_packet_unref(pkt);
        }
    } while (0);
    
    _playFlag = false;
    clearBuf();
    if(pFrame)
    {
        av_frame_free(&pFrame);
        av_free(pFrame);
        delete pFrame;
        pFrame = NULL;
    }
    
    if(pkt)
    {
        av_packet_free_side_data(pkt);
        av_packet_unref(pkt);
        av_packet_free(&pkt);
    }
    
    if(video_dec_ctx)
    {
        avcodec_close(video_dec_ctx);
        video_dec_ctx = NULL;
    }
    
    if(pFormat)
    {
        avformat_close_input(&pFormat);
        avformat_free_context(pFormat);
        delete pFormat;
        pFormat = NULL;
    }
    
    return 0;
}

void LiveVideo::clearBuf()
{
    std::vector::iterator it;
    for(it = _data.begin(); it != _data.end(); )
    {
        if(it->buf)
        {
            delete[] it->buf;
            it->buf = NULL;
        }
        it = _data.erase(it);
    }
}

void LiveVideo::loadShader()
{
    _glProgram = new GLProgram();
    _glProgram->initWithFilenames("shader/vertexShader.vsh", "shader/fragmentShader.fsh");
    _glProgram->link();
}

void LiveVideo::loadTexture()
{
    glGenTextures(1, &_textureY);
    glBindTexture(GL_TEXTURE_2D, _textureY);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    glGenTextures(1, &_textureU);
    glBindTexture(GL_TEXTURE_2D, _textureU);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    glGenTextures(1, &_textureV);
    glBindTexture(GL_TEXTURE_2D, _textureV);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

void LiveVideo::loadRectangle()
{
    //创建vao
    glGenVertexArrays(1, &_gVAO);
    glBindVertexArray(_gVAO);
    
    //创建vbo
    glGenBuffers(1, &_gVBO);
    glBindBuffer(GL_ARRAY_BUFFER, _gVBO);
    
    //创建顶点数组
    GLfloat vertex[] = {
        //  X     Y     Z       U     V
        -1.0f, 1.0f, 0.0f,   0.0f, 1.0f,
        1.0f, 1.0f, 0.0f,   1.0f, 1.0f,
        -1.0f, -1.0f, 0.0f,   0.0f, 0.0f,
        1.0f, -1.0f, 0.0f,   1.0f, 0.0f,
    };
    
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertex), vertex, GL_STATIC_DRAW);
    
    //绑定数据到vertexIn
    glEnableVertexAttribArray(_glProgram->getAttribLocation("vertexIn"));
    glVertexAttribPointer(_glProgram->getAttribLocation("vertexIn"), 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), NULL);
    
    //绑定数据到textureIn
    glEnableVertexAttribArray(_glProgram->getAttribLocation("textureIn"));
    glVertexAttribPointer(_glProgram->getAttribLocation("textureIn"), 2, GL_FLOAT, GL_TRUE, 5 * sizeof(GLfloat), (const GLvoid*)(3 * sizeof(GLfloat)));
    
    //unbind the VAO
    glBindVertexArray(0);
}

void LiveVideo::initCommand()
{
    _command.init(_globalZOrder);
    _command.func = CC_CALLBACK_0(LiveVideo::onDraw, this);
}

void LiveVideo::draw(Renderer *render, const Mat4 &transform, uint32_t flags)
{
    
    onDraw();
}

void LiveVideo::onDraw()
{
    now = getCurrentTime();
    
    char* buf = getBuff();
    
    
    if(buf ==NULL)
    {
        return;
    }
    // clear everything
    glClearColor(0, 0, 0, 1); // black
    glClear(GL_COLOR_BUFFER_BIT);
    
    // bind the program (the shaders)
    _glProgram->use();
    
    /**
     * 为了直播临时添加的bindLiveVideoTexture2DN
     */
    GL::bindLiveVideoTexture2DN(0, _textureY);
    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 0x1903,
                 _pixel_w,
                 _pixel_h,
                 0,
                 0x1903,
                 GL_UNSIGNED_BYTE,
                 buf);
    GLuint p =  glGetUniformLocation(_glProgram->getProgram(), "tex_y");
    glUniform1i(p, 0);
    
    
    GL::bindLiveVideoTexture2DN(1, _textureU);
    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 0x1903,
                 _pixel_w/2,
                 _pixel_h/2,
                 0,
                 0x1903,
                 GL_UNSIGNED_BYTE,
                 buf + _pixel_w*_pixel_h);
    GLuint p1 =  glGetUniformLocation(_glProgram->getProgram(), "tex_u");
    glUniform1i(p1, 1);
    
    
    GL::bindLiveVideoTexture2DN(2, _textureV);
    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 0x1903,
                 _pixel_w/2,
                 _pixel_h/2,
                 0,
                 0x1903,
                 GL_UNSIGNED_BYTE,
                 buf + _pixel_w*_pixel_h + _pixel_w*_pixel_h/4);
    GLuint p2 =  glGetUniformLocation(_glProgram->getProgram(), "tex_v");
    glUniform1i(p2, 2);
    
    
    buf = NULL;
    
    //    glProgram->setUniform("tex", 0); //set to 0 because the texture is bound to GL_TEXTURE0
    
    // bind the VAO (the triangle)
    glBindVertexArray(_gVAO);
    
    // draw the VAO
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    
    // unbind the VAO, the program and the texture
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
    glUseProgram(0);
}

char* LiveVideo::getBuff()
{
    char* buf = NULL;
    std::vector::iterator it;
    std::vector::iterator temp;
    for(it = _data.begin(); it != _data.end(); )
    {
        for(temp = _data.begin(); temp != _data.end();)
        {
            if(temp != it && temp->buf == it->buf)
            {
                CCLOG("~!!!有两个指针指向同一个地址");
                temp = _data.erase(temp);
            } else {
                ++temp;
            }
        }
        
        
        if(getDifferTime(it) < 0 && _data.size() > 1)
        {
            if(it->buf)
            {
                delete[] it->buf;
                it->buf = NULL;
            }
            it = _data.erase(it);
        }
        else
        {
            buf = it->buf;
            ++it;
            break;
        }
    }
    return buf;
}

long LiveVideo::getDifferTime(std::vector::iterator it)
{
    
    int64_t differTime = (it->pts - _start_time) - (now - _system_start_time);
    
    
    if(_data.size() > 40)
    {
        
        if(_data.size() <= 60)
        {
            if(_system_start_time > now - (_data.at(_data.size() - 25).pts - _start_time))
            {
                _system_start_time -= 20;
            }
            differTime = (it->pts - _start_time) - (now - _system_start_time + (_data.size()- 30)*20);
        }
        if(_data.size() > 60)
        {
            if(_system_start_time > now - (_data.at(_data.size() - 25).pts - _start_time))
            {
                _system_start_time -= 40;
            }
            differTime = (it->pts - _start_time) - (now - _system_start_time + (_data.size()- 30)*40);
        }
        
    }
    if(_data.size() > 30 && _data.size() <= 40)
    {
        differTime = (it->pts - _start_time) - (now - _system_start_time + (_data.size()- 30)*2);
    }
    
    if(_data.size() <= 15)
    {
        if(_system_start_time < now - (_data.begin()->pts - _start_time))
        {
            _system_start_time += 10;
        }
        differTime = (it->pts - _start_time) - (now - _system_start_time + (_data.size() - 15)*40);
    }
    return differTime;
}




LiveVideo.h

//
//  LiveVideo.h
//  Game
//
//  Created by zhufu on 2017/3/1.
//
//

#ifndef LiveVideo_h
#define LiveVideo_h

#include 
#include "ui/cocosGUI.h"

USING_NS_CC;

#endif /* LiveVideo_h */
class LiveVideo : public Node
{
private:
    struct FrameData {
        int64_t pts;
        char* buf;
    };
public:
    static Scene* createScene();
    static LiveVideo* create();
    virtual bool init() override;
    void initEvents();
    int ffmpegDecode(const char* filePath);
    void flipVertical(int width, int height, char* arr);
    
    virtual void draw(Renderer* renderer, const Mat4 &transform, uint32_t flags) override;
    
    void loadShader();
    void loadTexture();
    void loadRectangle();
    void onDraw();
    void initCommand();
    long getCurrentTime();
    long getDifferTime(std::vector::iterator it);
    char* getBuff();
    
    void addPlayButton();
    void addPlayHKSButton();
    void addStopButton();
    void addRefreshButton();
    
    void play();
    void playHKS();
    void stop();
    void refresh();
    
    void setPlay(bool playFlag);
    bool isPlay();
    
    void clearBuf();
private:
    CustomCommand _command;
    GLProgram* _glProgram;
    bool _playFlag = false;
    GLuint _textureY;
    GLuint _textureU;
    GLuint _textureV;
    GLuint _gVAO = 0;
    GLuint _gVBO = 0;
    int _pixel_w = 320, _pixel_h = 180;
    
    std::vector _data;
    int _frameRate;
    
    int64_t _start_time;
    long _system_start_time;
    char* currentLivePath = new char[256];
    
    long now;
};

之后,再在AppDelegate.cpp里引用LiveVideo.h文件:

cocos2dx跨平台直播实例-ffmpeg-ios篇_第2张图片

第二个框的配置:

fragmentShader.fsh

varying vec2 textureOut;
uniform sampler2D tex_y;
uniform sampler2D tex_u;
uniform sampler2D tex_v;
void main(void)
{
    vec3 yuv;
    vec3 rgb;
    yuv.x = texture2D(tex_y, textureOut).r;
    yuv.y = texture2D(tex_u, textureOut).r - 0.5;
    yuv.z = texture2D(tex_v, textureOut).r - 0.5;
    rgb = mat3( 1,       1,         1,
               0,       -0.39465,  2.03211,
               1.13983, -0.58060,  0) * yuv;
    gl_FragColor = vec4(rgb, 1);
}

vertexShader.vsh

attribute vec3 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
void main(void)
{
    gl_Position = vec4(vertexIn, 1);
    textureOut = textureIn;
}

右击Resources,出现如图:

cocos2dx跨平台直播实例-ffmpeg-ios篇_第3张图片

点击"Add Files to "Game",出现如图:

cocos2dx跨平台直播实例-ffmpeg-ios篇_第4张图片

选择fshader文件夹,再点Add,完成第二个框的配置。

每三个框的配置:

把开始编译好的FFmpeg-iOS文件夹复制到如图路径:

cocos2dx跨平台直播实例-ffmpeg-ios篇_第5张图片

再:

cocos2dx跨平台直播实例-ffmpeg-ios篇_第6张图片

选择FFmpeg-iOS文件夹加入到项目中:

cocos2dx跨平台直播实例-ffmpeg-ios篇_第7张图片

。。。。。

完成配置,运行。。。。。

最后,大家如果配置的时候出现问题可以留言或者直接下载项目https://github.com/zhu12345618/ffmpeg_ios_Test

你可能感兴趣的:(c++,ffmpeg,cocos2dx)