ffmpeg+sdl播放类

前段时间一直捣鼓ffmpeg,觉得还是VLC比较亲切,虽然我现在都不知道VLC怎么用了。

除了雷神的博客,主要参考的还是这个博客:http://blog.yundiantech.com/?log=index

自己在Qt下做了一个功能简单的播放类(播放,暂停,停止)。以后有时间再弄上快进,后退等。

/*********************************************************************************
  *Copyright(C),ForMySelf
  *FileName:GPlayer.h
  *Author:gongluck
  *Version:1.0
  *Date: 2017-06-15
  *Description:播放本地视频文件
  *Others:
  *Function List:
  *History:
     1.Date: 2017-06-15
       Author: gongluck
       Modification: 很早之前做的了,现在整理一下
     2.Date: 2017-06-16
       Author: gongluck
       Modefication: 加上暂停等功能
**********************************************************************************/

#ifndef GPLAYER_H
#define GPLAYER_H

//Qt
#include <QObject>
#include <QImage>

//stl
#include <Queue>
using namespace std;

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"

#include "SDL.h"
#undef main
}

class GPlayer : public QObject
{
    Q_OBJECT
public:
    explicit GPlayer(QObject *parent = 0);
    ~GPlayer();

    bool SetFilename(const char* filename);
    bool PlayFile(const char* filename = NULL);
    bool Stop();

    AVFormatContext* GetAVFormatCtx(){ return m_pFormatCtx; }
    AVCodecContext* GetVCodecCtx() { return m_pVCodecCtx; }
    AVCodecContext* GetACodecCtx() { return m_pACodecCtx; }
    queue<AVPacket>& GetVPacketQue(){ return m_VpacketQue; }
    queue<AVPacket>& GetAPacketQue(){ return m_ApacketQue; }

    int GetVIndex() { return m_vindex; }
    int GetAIndex() { return m_aindex; }
    int GetDstWidth() { return m_dstWidth; }
    int GetDstHeight() { return m_dstHeight; }
    void SetDstWidth(int width) { m_dstWidth = width; }
    void SetDstHeight(int height) { m_dstHeight = height; }
    SDL_AudioSpec GetAudioSpec() { return m_AudioSpec; }
    void SetPts(double pts) { m_pts = pts; }
    double GetPts() { return m_pts; }
    bool GetStopFlag() { return m_bStop; }
    void SetStopFlag(bool bStop) { m_bStop = bStop; }
    bool GetPauseFlag() { return m_bPause; }
    void SetPauseFlag(bool bPause)
    {
        if(bPause)
            SDL_PauseAudio(1);
        else
            SDL_PauseAudio(0);
        m_bPause = bPause;
    }
    bool GetPlayingFlag() { return m_bPlaying; }
    void SetPlayingFlag(bool bPause) { m_bPlaying = bPause; }

signals:
    void sig_picture(QImage);
public slots:

protected:
    bool PreV();
    bool PreA();

private:
    bool m_bInit;
    char* m_filename;
    unsigned int m_filenameLength;

    AVFormatContext* m_pFormatCtx;
    AVCodecContext* m_pVCodecCtx;
    AVCodecContext* m_pACodecCtx;
    AVCodec* m_pVCodec;
    AVCodec* m_pACodec;

    SDL_AudioSpec m_AudioSpec;
    SDL_Thread* m_TReadPacket;
    SDL_Thread* m_TPlayVideo;

    queue<AVPacket> m_VpacketQue;
    queue<AVPacket> m_ApacketQue;

    int m_vindex;
    int m_aindex;
    int m_dstWidth;
    int m_dstHeight;

    double m_pts;
    bool m_bStop;
    bool m_bPause;
    bool m_bPlaying;
};

#endif // GPLAYER_H
/*********************************************************************************
  *Copyright(C),ForMySelf
  *FileName:GPlayer.cpp
  *Author:gongluck
  *Version:1.0
  *Date: 2017-06-15
  *Description:播放本地视频文件
  *Others:
  *Function List:
  *History:
     1.Date: 2017-06-15
       Author: gongluck
       Modification: 很早之前做的了,现在整理一下
     2.Date: 2017-06-16
       Author: gongluck
       Modefication: 加上暂停等功能
**********************************************************************************/

#include "GPlayer.h"

int SDLCALL ReadPacket(void *data)
{
    AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    GPlayer* player = (GPlayer*)data;
    AVFormatContext* pFormatCtx = player->GetAVFormatCtx();
    queue<AVPacket>& VpacketQue = player->GetVPacketQue();
    queue<AVPacket>& ApacketQue = player->GetAPacketQue();
    int vindex = player->GetVIndex();
    int aindex = player->GetAIndex();

    av_init_packet(packet);
    while(av_read_frame(pFormatCtx,packet) >= 0)
    {
        if(player->GetStopFlag())
            break;
        while(player->GetPauseFlag())
        {
            SDL_Delay(10);
        }
        if(packet->stream_index == vindex)
        {
            while(VpacketQue.size()>1023)
            {
                if(player->GetStopFlag())
                    break;
                SDL_Delay(10);
            }
            VpacketQue.push(*packet);
        }
        else if(packet->stream_index == aindex)
        {
            while(ApacketQue.size()>1023)
            {
                if(player->GetStopFlag())
                    break;
                SDL_Delay(10);
            }
            ApacketQue.push(*packet);
        }
        else
        {
            av_free_packet(packet);
        }
    }
    av_free(packet);
    return 0;
}

int SDLCALL PlayVideo(void *data)
{
    AVFrame frame = {0};
    AVFrame frameRGB = {0};
    AVPacket packet = {0};
    GPlayer* player = (GPlayer*)data;
    AVFormatContext* pFormatCtx = player->GetAVFormatCtx();
    AVCodecContext* pVCodecCtx = player->GetVCodecCtx();
    queue<AVPacket>& VpacketQue = player->GetVPacketQue();
    int vindex = player->GetVIndex();
    int got_picture = 0;
    struct SwsContext* img_convert_ctx = NULL;
    int size = 0;
    uint8_t* buf = NULL;

    img_convert_ctx = sws_getContext(pVCodecCtx->width,pVCodecCtx->height,pVCodecCtx->pix_fmt
                                     ,player->GetDstWidth(),player->GetDstHeight(),AV_PIX_FMT_RGB32,SWS_BICUBIC
                                     ,NULL,NULL,NULL);
    size = avpicture_get_size(AV_PIX_FMT_RGB32,player->GetDstWidth(),player->GetDstHeight());
    buf = (uint8_t*)av_mallocz(sizeof(uint8_t) * size);
    avpicture_fill((AVPicture*)&frameRGB,buf,AV_PIX_FMT_RGB32,player->GetDstWidth(),player->GetDstHeight());//把buf和frameRGB绑定

    while(true)
    {
        if(player->GetStopFlag())
            break;
        else if(player->GetPauseFlag())
        {
            SDL_Delay(10);
            continue;
        }
        if(VpacketQue.size() == 0)
        {
            SDL_Delay(10);
            continue;
        }
        packet = VpacketQue.front();
        if(avcodec_decode_video2(pVCodecCtx,&frame,&got_picture,&packet)<0)
        {
            av_free_packet(&packet);
            VpacketQue.pop();
            continue;
        }
        if(got_picture)
        {
            sws_scale(img_convert_ctx,frame.data,frame.linesize,0,pVCodecCtx->height,frameRGB.data,frameRGB.linesize);
            QImage img(buf,player->GetDstWidth(),player->GetDstHeight(),QImage::Format_RGB32);
//            while(frame.pkt_pts * av_q2d(pFormatCtx->streams[vindex]->time_base) > player->GetPts())
//                SDL_Delay(1);
            if(frame.pkt_pts * av_q2d(pFormatCtx->streams[vindex]->time_base) > player->GetPts())
                  SDL_Delay((frame.pkt_pts*av_q2d(pFormatCtx->streams[vindex]->time_base) - player->GetPts()) *1000);
            emit player->sig_picture(img);
            VpacketQue.pop();
        }
    }

    return 0;
}

void SDLCALL audioCallback(void *userdata, Uint8 * stream, int len)
{
    AVFrame frame = {0};
    AVPacket packet = {0};
    GPlayer* player = (GPlayer*)userdata;
    AVFormatContext* pFormatCtx = player->GetAVFormatCtx();
    AVCodecContext* pACodecCtx = player->GetACodecCtx();
    queue<AVPacket>& ApacketQue = player->GetAPacketQue();
    int aindex = player->GetAIndex();
    int got_picture = 0;
    SDL_AudioSpec audiospec = player->GetAudioSpec();
    static struct SwrContext* swr_convert_ctx = swr_alloc_set_opts(NULL,av_get_default_channel_layout(audiospec.channels),AV_SAMPLE_FMT_S16,audiospec.freq
                                                            ,av_get_default_channel_layout(pACodecCtx->channels),pACodecCtx->sample_fmt,pACodecCtx->sample_rate
                                                            ,NULL,NULL);
    swr_init(swr_convert_ctx);
    int size = av_samples_get_buffer_size(NULL,audiospec.channels,audiospec.samples,AV_SAMPLE_FMT_S16,1);
    uint8_t* buf = (uint8_t*)av_mallocz(sizeof(uint8_t) * size);

    while(ApacketQue.size() == 0)
    {
        if(player->GetStopFlag())
            break;
        SDL_Delay(10);
    }
    packet = ApacketQue.front();
    if( avcodec_decode_audio4(pACodecCtx,&frame,&got_picture,&packet) < 0 )
    {
        av_free_packet(&packet);
        ApacketQue.pop();
        return ;
    }
    if(got_picture)
    {
        swr_convert(swr_convert_ctx,&buf,size,(const uint8_t**)frame.data,frame.nb_samples);
        SDL_memset(stream,0,len);
        SDL_MixAudio(stream,buf,len,SDL_MIX_MAXVOLUME);
        player->SetPts(frame.pkt_pts * av_q2d(pFormatCtx->streams[aindex]->time_base));
    }
    av_free_packet(&packet);
    ApacketQue.pop();
    if(player->GetPauseFlag())
        SDL_PauseAudio(1);
    else
        SDL_PauseAudio(0);
}

GPlayer::GPlayer(QObject *parent) : QObject(parent)
  ,m_bInit(false)
  ,m_filename(NULL)
  ,m_filenameLength(0)
  ,m_pFormatCtx(NULL)
  ,m_vindex(-1)
  ,m_aindex(-1)
  ,m_pVCodecCtx(NULL)
  ,m_pVCodec(NULL)
  ,m_pACodecCtx(NULL)
  ,m_pACodec(NULL)
  ,m_AudioSpec{0}
  ,m_pts(0.0)
  ,m_bPause(false)
  ,m_bStop(false)
{
    //初始化ffmpeg
    av_register_all();

    //初始化SDL
    if(SDL_Init(SDL_INIT_AUDIO) != 0)
    {
        m_bInit = false;
    }
    else
    {
        m_bInit = true;
    }
}

GPlayer::~GPlayer()
{
    if(m_filename != NULL)
    {
        free(m_filename);
        m_filename = NULL;
        m_filenameLength = 0;
    }
}

bool GPlayer::SetFilename(const char* filename)
{
    if(m_filename != NULL)
    {
        free(m_filename);
        m_filename = NULL;
        m_filenameLength = 0;
    }
    m_filenameLength = strlen(filename)+1;
    if(m_filenameLength<=0 || m_filenameLength>260)
        return false;
    m_filename = (char*)malloc(m_filenameLength);
    if(m_filename == NULL)
        return false;
    strncpy(m_filename,filename,m_filenameLength);
    return true;
}

bool GPlayer::PlayFile(const char* filename)
{
    if(filename != NULL)
    {
        if(!SetFilename(filename))
            return false;
    }
    if(m_pFormatCtx != NULL)
        goto ERROR;
    if(avformat_open_input(&m_pFormatCtx,m_filename,NULL,NULL)<0)
        goto ERROR;
    if(avformat_find_stream_info(m_pFormatCtx,NULL)<0)
        goto ERROR;

    if(!PreV() || !PreA())
        goto ERROR;

    SetStopFlag(false);
    SetPauseFlag(false);
    SetPlayingFlag(true);
    m_TReadPacket = SDL_CreateThread(ReadPacket,"ReadPacket",this);
    m_TPlayVideo = SDL_CreateThread(PlayVideo,"PlayVideo",this);
    SDL_PauseAudio(0);

    return true;
ERROR:
    if(m_pFormatCtx != NULL)
        avformat_close_input(&m_pFormatCtx);
    m_pVCodecCtx = m_pACodecCtx = NULL;
    m_pVCodec = m_pACodec = NULL;
    m_vindex = -1;
    m_aindex = -1;
    return false;
}

bool GPlayer::PreV()
{
    for(int i=0;i<m_pFormatCtx->nb_streams;++i)
    {
        if(m_pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
            m_aindex = i;
        else if(m_pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
            m_vindex = i;
    }
    if(m_vindex==-1 || m_aindex==-1)
        goto ERROR;

    m_pVCodecCtx = m_pFormatCtx->streams[m_vindex]->codec;
    m_pVCodec = avcodec_find_decoder(m_pVCodecCtx->codec_id);
    if(avcodec_open2(m_pVCodecCtx,m_pVCodec,NULL) <0)
        goto ERROR;
    m_pACodecCtx = m_pFormatCtx->streams[m_aindex]->codec;
    m_pACodec = avcodec_find_decoder(m_pACodecCtx->codec_id);
    if(avcodec_open2(m_pACodecCtx,m_pACodec,NULL) <0)
        goto ERROR;

    return true;
ERROR:
    return false;
}

bool GPlayer::PreA()
{
    SDL_AudioSpec want = {0};
    want.callback = audioCallback;
    want.channels = m_pACodecCtx->channels;
    want.format = AUDIO_S16SYS;//ffmpeg的格式与SDL的格式 不相同
    want.freq = m_pACodecCtx->sample_rate;
    want.samples = m_pACodecCtx->frame_size;//!!!
    want.userdata = this;
    if(SDL_OpenAudio(&want,&m_AudioSpec) != 0)
        goto ERROR;
    return true;
ERROR:
    return false;
}

bool GPlayer::Stop()
{
    m_bStop = true;
    m_bPlaying = false;
    SDL_CloseAudio();

    if(m_pVCodecCtx != NULL)
        avcodec_close(m_pVCodecCtx);
    if(m_pACodecCtx != NULL)
        avcodec_close(m_pACodecCtx);
    if(m_pFormatCtx != NULL)
        avformat_close_input(&m_pFormatCtx);

    m_pFormatCtx = NULL;
    m_pVCodecCtx = NULL;
    m_pACodecCtx = NULL;
    m_pVCodec = NULL;
    m_pACodec = NULL;

    memset(&m_AudioSpec,0,sizeof(m_AudioSpec));
    m_TReadPacket = NULL;
    m_TPlayVideo = NULL;

    while(!m_VpacketQue.empty())
    {
        av_free_packet(&m_VpacketQue.front());
        m_VpacketQue.pop();
    }
    while(!m_ApacketQue.empty())
    {
        av_free_packet(&m_ApacketQue.front());
        m_ApacketQue.pop();
    }

    m_vindex = -1;
    m_aindex = -1;
//    m_dstWidth = 0;
//    m_dstHeight = 0;

    m_pts = 0;
    return true;
}

我自己最不喜欢别人不加注释,但是自己平常也不怎么写注释。

工程地址

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据库新发现

关于dirty buffer

SQL> select VIEW_DEFINITION from v$fixed_view_definition where VIEW_NAME = 'GV$B...

983
来自专栏xingoo, 一个梦想做发明家的程序员

Oracle基础知识-sqlplus使用

Oracle的sql*plus是与oracle进行交互的客户端工具。 在sql*plus中,可以运行sql*plus命令与sql*plus语句。 我们通常所说的...

22310
来自专栏乐沙弥的世界

对比 PL/SQL profiler 剖析结果

      使用PL/SQL PROFILER 剖析PL/SQL代码是快速定位PL/SQL代码段最有效的方法。在上一篇文章使用PL/SQL PROFILER 定...

1141
来自专栏乐沙弥的世界

PL/SQL 包编译时hang住的处理

       最近PL/SQL包在编译时被hang住,起初以为是所依赖的对象被锁住。结果出乎意料之外。下面直接看代码演示。

866
来自专栏数据库新发现

字符集问题的初步探讨(二)

原文发表于itpub技术丛书《Oracle数据库DBA专题技术精粹》,未经许可,严禁转载本文.

1232
来自专栏乐沙弥的世界

cannot fetch plan for SQL_ID

  SQL tuning过程中离不开分析SQL语句的执行计划。在一次提取执行计划的时候碰到cannot fetch plan for SQL_ID的错误提示。根...

852
来自专栏大内老A

谈谈基于SQL Server 的Exception Handling[中篇]

三、TRY CATCH & Return 在上面一节中,我通过RAISERROR重写了创建User的Stored procedure,实际上上面的Stored ...

1796
来自专栏乐沙弥的世界

ORA-00932: inconsistent datatypes: expected - got CLOB

      最近数据库从10.2.0.3升级到了10.2.0.5之后,一些对象无法编译通过。查看了这些对象主要表现在之前写法不严格的SQL语法导致了这些pack...

1343
来自专栏乐沙弥的世界

Linux/Unix shell 自动发送AWR report(二)

       观察Oracle数据库性能,Oracle自带的awr 功能为我们提供了一个近乎完美的解决方案,通过awr特性我们可以随时从数据库提取awr报告。不...

853
来自专栏乐沙弥的世界

mysqldump 快速搭建特定库主从架构(GTID)

相关知识点参考 基于mysqldump搭建gtid主从 MySQL GTID 错误处理汇总 配置MySQL GTID 主从复制 使用mysqld...

960

扫码关注云+社区