ffmpeg && cocos2d 동영상 플레이어

2023. 3. 14. 19:26cocos2dx

소리 재생부분은 일단은 필요없어서 넣지 않았음.

linker에 연결할 부분은 

avcodec.lib
avformat.lib
avutil.lib
swscale.lib

ffmpeg 3.4.1 32bit shared library를 갖고 작업함. 여기 올리고 싶은데 압축파일이 커서...


#include "cocos2d.h"

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/avutil.h"
#include "libavutil/imgutils.h"
}

class LayerMovie : public cocos2d::Layer
{
public:
    static LayerMovie* create(const std::string fileName);
    LayerMovie(const std::string fileName) : v_spr(NULL)
    {
        v_name = fileName;
        // Initalizing these to NULL prevents segfaults!
        pFormatCtx = NULL;
        pCodecCtxOrig = pCodecCtx = NULL;
        pCodec = NULL;
        pFrame = pFrameRGB = NULL;
        buffer = NULL;
        sws_ctx = NULL;
    }
    ~LayerMovie()
    {
        // Free the RGB image
        if (buffer) av_free(buffer);
        if (pFrameRGB) av_frame_free(&pFrameRGB);
        // Free the YUV frame
        if (pFrame) av_frame_free(&pFrame);
        // Close the codecs
        if (pCodecCtx) avcodec_close(pCodecCtx);
        if (pCodecCtxOrig) avcodec_close(pCodecCtxOrig);
        // Close the video file
        if (pFormatCtx) avformat_close_input(&pFormatCtx);
    }
public:
    std::string v_name;
    cocos2d::Sprite* v_spr;
    bool _loop;

    //FFmpeg movie datas
    AVFormatContext   *pFormatCtx;    
    AVCodecContext    *pCodecCtxOrig, *pCodecCtx;
    AVCodec           *pCodec;
    AVFrame           *pFrame, *pFrameRGB;
    AVPacket          packet;
    struct SwsContext *sws_ctx;
    uint8_t           *buffer;
    int               videoStream;
    int               frameFinished;
    int               numBytes;

public:
    bool init();
    void updateFrame(float dt);
    bool setLoop(bool v = true);    
    void setWidth(float width);
    void setSize(cocos2d::Size size);
    float getWidth();
    float getHeight();
};










#include "LayerMovie.h"

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

LayerMovie* LayerMovie::create(const std::string fileName)
{
    LayerMovie *pRet = new LayerMovie(fileName);
    if (pRet && pRet->init())
    {
        pRet->autorelease();
        return pRet;
    }
    else
    {
        delete pRet;
        pRet = nullptr;
        return nullptr;
    }
}

bool LayerMovie::init()
{
    // Register all formats and codecs
    av_register_all();

    // Open video file
    if (avformat_open_input(&pFormatCtx, v_name.c_str(), NULL, NULL) != 0) return false;
    // Retrieve stream information
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) return false;
    
    // Dump information about file onto standard error
    av_dump_format(pFormatCtx, 0, v_name.c_str(), 0);

    // Find the first video stream
    videoStream = -1;
    for (int i = 0; i<pFormatCtx->nb_streams; i++)
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
            break;
        }
    if (videoStream == -1) return false; // Didn't find a video stream

    // set Frame update time(for cocos2d)
    double uTime = 1.0 / av_q2d(pFormatCtx->streams[videoStream]->avg_frame_rate);

    // Get a pointer to the codec context for the video stream
    pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec;
    // Find the decoder for the video stream
    pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
    if (pCodec == NULL) {
        printf("Unsupported codec!\n");
        return false; // Codec not found
    }
    // Copy context
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
        printf("Couldn't copy codec context\n");
        return false; // Error copying codec context
    }

    // Open codec
    if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) return false;       

    // Allocate video frame
    pFrame = av_frame_alloc();    

    // pCodec -> target 으로 변환하는 규칙 설정이다.
    sws_ctx = sws_getContext(pCodecCtx->width,
        pCodecCtx->height,
        pCodecCtx->pix_fmt,
        pCodecCtx->width,
        pCodecCtx->height,
        AV_PIX_FMT_RGBA,
        SWS_BILINEAR,
        NULL,
        NULL,
        NULL
        );

    //이미지용 프레임, 버퍼크기, 버퍼 할당
    //프레임 구조
    pFrameRGB = av_frame_alloc();
    if (pFrame == NULL || pFrameRGB == NULL) return false;
    //버퍼 크기
    numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecCtx->width, 
        pCodecCtx->height, 1);
    //버퍼
    buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
    //Setup the data pointers and linesizes based on the specified image
    av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize,
        buffer, AV_PIX_FMT_BGRA, pCodecCtx->width, pCodecCtx->height, 1);    

    //첫번째 프레임을 읽고 cocos2d::Texture2D, Sprite를 생성해서 복사하기
    //read frame
    while (true)
    {
        if (av_read_frame(pFormatCtx, &packet) >= 0)
        {
            if (packet.stream_index == videoStream) {
                avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
                if (frameFinished) {
                    sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
                        pFrame->linesize, 0, pCodecCtx->height,
                        pFrameRGB->data, pFrameRGB->linesize);   
                }
            }
            av_free_packet(&packet);
            break;
        }
    }
    //to Sprite
    cocos2d::Texture2D* dTex = new cocos2d::Texture2D();
    if (dTex->initWithData(pFrameRGB->data[0], 
        pCodecCtx->width * pCodecCtx->height * 4,
        cocos2d::Texture2D::PixelFormat::RGBA8888, 
        pCodecCtx->width,
        pCodecCtx->height, 
        cocos2d::Size(pCodecCtx->width, pCodecCtx->height)) == false) return false;
    dTex->autorelease();
    v_spr = cocos2d::Sprite::createWithTexture(dTex);
    this->addChild(v_spr);
    
    //프레임 업데이트 설정
    schedule(schedule_selector(LayerMovie::updateFrame), uTime);
    return true;    
}
void LayerMovie::updateFrame(float dt)
{
    //read 1st frame
    while (true)
    {
        if (av_read_frame(pFormatCtx, &packet) >= 0)
        {
            if (packet.stream_index == videoStream) {
                avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
                if (frameFinished) {
                    sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
                        pFrame->linesize, 0, pCodecCtx->height,
                        pFrameRGB->data, pFrameRGB->linesize);
                }
            }
            av_free_packet(&packet);
            break;
        }
        else
        {
            if (_loop) av_seek_frame(
                pFormatCtx, videoStream, pFormatCtx->start_time, AVSEEK_FLAG_FRAME);
            else this->removeFromParentAndCleanup(true);
            return;
        }
    }
    v_spr->getTexture()->updateWithData(
        pFrameRGB->data[0], 0, 0,
        pCodecCtx->width, pCodecCtx->height);
}