cocos2dx windows 播放mp4視頻


mp4解碼過程主要通過ffmpeg實現,解碼轉出rgba格式后,通過CCSprite繪制出來。

 

VideoPanel.h
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include<libavfilter/avfilter.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include "libavutil/imgutils.h"
};


struct FrameData
{
    void*    pData;
    int        iLen;
    int        iWidth;
    int        iHeight;
    int        iTime; //顯示時間戳\0
    int        iIndex;

    FrameData(void*    _pData, int    _iLen, int _iWidth, int _iHeight, float _iTime, int _iIndex)
    {
        iLen = _iLen;
        iWidth = _iWidth;
        iHeight = _iHeight;
        iTime = _iTime;
        iIndex = _iIndex;

        pData = malloc(iLen);
        memcpy(pData, _pData, iLen);
    }
};


class VideoPanel : public Layer
{
public:
    VideoPanel();
    ~VideoPanel();

    static VideoPanel*        create();
    bool                    init();
    bool                    play(const std::string& strUrl);
    void                    pause();

    void                    resize(int iWidth, int iHeight);

    bool                    readFile(const std::string& strUrl);
    void                    run(float fDelay);
    void                    sliderEvent(Ref *pSender, ui::Slider::EventType type);
    void                    menuCallback(Ref* pSender);
    void                    closeCallback(Ref* pSender);

    void                    cacheFrameData();//用線程緩存幀數據
    void                    setTipKey(const std::string& strKey);

private:
    Sprite*                m_pVideoSp;

    AVFormatContext*    m_pFormatContext;
    AVCodecContext*        m_pVideoCodecContext;
    AVCodecContext*        m_pAudioCodecContext;

    int                    m_iVideoIndex;
    int                    m_iAudioIndex;

    AVFrame*            m_pVideoFrame;
    AVFrame*            m_pVideoFrameYUV;
    AVFrame*            m_pAudioFrame;

    int                    m_iOutBufferSize;
    void*                m_pOutBuffer;
    SwsContext*            m_pVideoSwSContext;
    SwrContext*            m_pAudioSwrContext;

    int                    m_iOutChannelNum;//輸出的聲道個數\0
    void*                m_pOutAudioBuffer;

    AVPacket*            m_pPacket;
    bool                m_bPause;
    bool                m_bExitThread;
    AVSampleFormat        m_OutSampleFormat;//音頻輸出采樣率格式\0


    vector<FrameData*>    m_vtFrameData;
    int                    m_iCurFrameIndex;

    int                    m_iTotalTime;//視頻總時長\0
    int                    m_iTotalFrameNum;//視頻總幀數\0
    AVRational            m_sTimeBase;//時間基\0
    int                    m_iFrameRate;//幀率\0

};

VideoPanel.cpp

#include "VideoPanel.h"


#define MAX_CACHE_FRAME_NUM 300        


VideoPanel::VideoPanel()
    : m_pVideoSp(nullptr)
    , m_iCurFrameIndex(0)
{
}

VideoPanel::~VideoPanel()
{
    for (FrameData* pData : m_vtFrameData)
    {
        free(pData->pData);
        delete pData;
    }

    m_vtFrameData.clear();
}

VideoPanel* VideoPanel::create()
{
    VideoPanel *pNode = new VideoPanel();
    if (pNode != nullptr && pNode->init())
    {
        pNode->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(pNode);
    }

    return pNode;
}

bool VideoPanel::init()
{
    RETURN_IF(!Layer::init(), false);

    return true;
}

bool VideoPanel::play(const std::string& strUrl)
{
    if (!readFile(strUrl))
    {
        return false;
    }

    m_bPause = false;

    _scheduler->schedule(schedule_selector(VideoPanel::run), this, 1.0f / m_iFrameRate, false);

    m_bExitThread = false;
    thread t(&VideoPanel::cacheFrameData, this);
    t.join();

    return true;
}

void VideoPanel::pause()
{
    m_bPause = true;
}


bool VideoPanel::readFile(const std::string& strUrl)
{
    AVFormatContext* pContext = avformat_alloc_context();  // 獲取文件信息上下文初始化
    m_pFormatContext = pContext;

    int iError = avformat_open_input(&m_pFormatContext, strUrl.c_str(), nullptr, nullptr);
    if (iError != 0)
    {
        LOG_ERROR("avformat_open_input fail");
        return false;
    }

    m_iVideoIndex = -1;
    m_iAudioIndex = -1;

    // 獲取流的通道
    for (int iIndex = 0; iIndex < pContext->nb_streams; iIndex++)
    {
        if (pContext->streams[iIndex]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            m_iVideoIndex = iIndex;
            LOG_INFO("video index : %d", m_iVideoIndex);
        }
        if (pContext->streams[iIndex]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            m_iAudioIndex = iIndex;
            LOG_INFO("audio index : %d", m_iAudioIndex);
        }
    }

    if (m_iVideoIndex == -1)
    {
        LOG_INFO("Couldn't find a video stream");
        return false;
    }

    // 視頻流處理
    if (m_iVideoIndex > -1)
    {
        //獲取視頻流中的編解碼上下文
        AVCodecParameters* pCodecPar = pContext->streams[m_iVideoIndex]->codecpar;

        //根據編解碼上下文中的編碼id查找對應的解碼
        AVCodec* pCodecVideo = avcodec_find_decoder(pCodecPar->codec_id);
        if (pCodecVideo == nullptr)
        {
            LOG_INFO("Couldn't find a video codec");
            return false;
        }

        m_pVideoCodecContext = avcodec_alloc_context3(pCodecVideo);
        //事實上codecpar包含了大部分解碼器相關的信息,這里是直接從AVCodecParameters復制到AVCodecContext
        avcodec_parameters_to_context(m_pVideoCodecContext, pCodecPar);
        //av_codec_set_pkt_timebase(m_pVideoCodecContext, pContext->streams[m_iVideoIndex]->time_base);

        //打開編碼器
        if (avcodec_open2(m_pVideoCodecContext, pCodecVideo, nullptr) < 0)
        {
            cout << "編碼器無法打開" << endl;
            return -1;
        }

        //輸出視頻信息
        const char* pszFormat = pContext->iformat->name;
        m_iTotalTime = (pContext->duration) / 1000000;
        int iWidth = m_pVideoCodecContext->width;
        int iHeight = m_pVideoCodecContext->height;
        m_sTimeBase = pContext->streams[m_iVideoIndex]->time_base;
        m_iFrameRate = pContext->streams[m_iVideoIndex]->r_frame_rate.num;
        m_iTotalFrameNum = pContext->streams[m_iVideoIndex]->nb_frames;
        m_pProgressBar->setMaxPercent(m_iTotalTime);

        LOG_INFO("video format:%s, length:%d, width:%d, height:%d", pszFormat, m_iTotalTime, iWidth, iHeight);

        //准備讀取
        //AVFrame用於存儲解碼后的像素數據(YUV)
        m_pVideoFrame = av_frame_alloc();//內存分配
        //YUV420
        m_pVideoFrameYUV = av_frame_alloc();
        //只有指定了AVFrame的像素格式、畫面大小才能真正分配內存
        //緩沖區分配內存
        m_iOutBufferSize = av_image_get_buffer_size(AV_PIX_FMT_RGBA, iWidth, iHeight, 1);

        m_pOutBuffer = av_malloc(m_iOutBufferSize);
        //初始化緩沖區
        av_image_fill_arrays(m_pVideoFrameYUV->data, m_pVideoFrameYUV->linesize,
            (uint8_t*)m_pOutBuffer, AV_PIX_FMT_RGBA,
            iWidth, iHeight, 1);
        //用於轉碼(縮放)的參數,轉之前的寬高,轉之后的寬高,格式等
        m_pVideoSwSContext = sws_getContext(iWidth, iHeight, AV_PIX_FMT_YUV420P /*pCodecCtx->pix_fmt*/,
            iWidth, iHeight, AV_PIX_FMT_RGBA, SWS_BICUBIC, nullptr, nullptr, nullptr);
    }

    //緩沖區,開辟空間
    m_pPacket = (AVPacket*)av_malloc(sizeof(AVPacket));

    return true;
}

void VideoPanel::run(float fDelay)
{
    RETURN_VOID_IF(m_bPause);
    RETURN_VOID_IF(m_iCurFrameIndex >= m_vtFrameData.size());

    FrameData* pFrameData = m_vtFrameData[m_iCurFrameIndex];
    int iWidth = pFrameData->iWidth;
    int iHeight = pFrameData->iHeight;

    if (m_pVideoSp == nullptr)
    {
        Image* image = new (std::nothrow) Image;
        image->initWithRawData((const unsigned char *)pFrameData->pData, pFrameData->iLen,
            iWidth, iHeight, 0, true);

        _director->getTextureCache()->removeTextureForKey("videoTexture");
        Texture2D* pTexture = _director->getTextureCache()->addImage(image, "videoTexture");

        m_pVideoSp = Sprite::createWithTexture(pTexture);
        m_pVideoSp->setAnchorPoint(Vec2::ZERO);
        m_pVideoSp->setPosition(Vec2(0, BOTTOM_HEIGHT));
        addChild(m_pVideoSp);

        if (iWidth < DEFAULT_WIDTH)
        {
            float fScale = (float)(DEFAULT_WIDTH / iWidth);
            m_pVideoSp->setScale(fScale);

            int width = iWidth * fScale;
            int height = iHeight * fScale;
            resize(width, height + BOTTOM_HEIGHT);
        }
        else
        {
            resize(iWidth, iHeight + BOTTOM_HEIGHT);
        }

        delete image;
    }
    else
    {
        m_pVideoSp->getTexture()->updateWithData(pFrameData->pData, 0, 0, iWidth, iHeight);
    }


    m_iCurFrameIndex++;
}


void VideoPanel::cacheFrameData()
{
    AVStream* pVideoStream = m_pFormatContext->streams[m_iVideoIndex];
    int iIndex = 0;

    while (av_read_frame(m_pFormatContext, m_pPacket) >= 0 && !m_bExitThread)
    {
        int iRet = 0;
        if (m_pPacket->stream_index == m_iVideoIndex)
        {
            //解碼一幀視頻壓縮數據,得到視頻像素數據
            iRet = avcodec_send_packet(m_pVideoCodecContext, m_pPacket);
            //iRet = avcodec_decode_video2(m_pVideoCodecContext, m_pVideoFrame, &iCurFrame, m_pPacket);
            if (iRet != 0)
            {
                LOG_ERROR("av send packet error");
                return;
            }

            //讀取到一幀音頻或者視頻
            if (avcodec_receive_frame(m_pVideoCodecContext, m_pVideoFrame) == 0) {
                //AVFrame轉為像素格式RGB24,寬高
                sws_scale(m_pVideoSwSContext, m_pVideoFrame->data, m_pVideoFrame->linesize,
                    0, m_pVideoCodecContext->height, m_pVideoFrameYUV->data, m_pVideoFrameYUV->linesize);

                int iWidth = m_pVideoCodecContext->width;
                int iHeight = m_pVideoCodecContext->height;

                FrameData* pFrameData = new FrameData(m_pOutBuffer, m_iOutBufferSize, iWidth, iHeight, 
                    m_pVideoFrame->pts * av_q2d(m_sTimeBase), iIndex++);
                m_vtFrameData.push_back(pFrameData);
            }
        }
    }
    
    av_packet_unref(m_pPacket);
}

#endif

 

調用方法:

VideoPanel* player = VideoPanel::create();
player->play("E:\\cocosvideo.mp4");
Director::getInstance()->getRunningScene()->addChild(player);
player->show();

 

最終加上進度條和按鈕的效果:

 

 

如需要支持有償服務,加q:980550823

轉載請注明出處,from 博客園HemJohn

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM