sdl+ffmpeg视频播放器02

8 篇文章 0 订阅
4 篇文章 0 订阅

主机环境:Windows XP

SDL版本:SDL2-2.0.3

ffmpeg版本:ffmpeg.2.4

ffmpeg库版本:ffmpeg-20140916-git-b76d613-win32-dev.7z、ffmpeg-20140916-git-b76d613-win32-shared.7z

开发环境:CodeBlocks13.12

将前面的代码重写成线程形式,主要是修改视频流的解码操作,区别主要在于视频流解包后不是直接显示,而是以事件驱动形式刷新图像,音频流的解码处理与之前的类似。

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/file.h>
#include <libavutil/avstring.h>
#include <libavutil/mem.h>
#include <libswscale/swscale.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
#include <stdbool.h>

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define    rmask 0xff000000
#define    gmask 0x00ff0000
#define    bmask 0x0000ff00
#define    amask 0x000000ff
#else
#define    rmask  0x000000ff
#define    gmask  0x0000ff00
#define    bmask  0x00ff0000
#define    amask  0xff000000
#endif


#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 /*音频缓存区*/
#define VIDEO_PICTURE_QUEUE_SIZE 3  /*视频图像队列大小*/

#define FF_ALLOC_EVENT (SDL_USEREVENT)
#define FF_REFRESH_EVENT (SDL_USEREVENT + 1)
#define FF_QUIT_EVENT (SDL_USEREVENT + 2)

#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)

#define SCREEN_WIDTH 640    /*初始化SDL时画面宽度*/
#define SCREEN_HEIGHT 480   /*初始化SDL时画面高度*/
typedef struct PacketQueue
{
    AVPacketList *first_pkt, *last_pkt;//首尾指针
    int nb_packets;//包数
    int size;//包中的字节数
    SDL_mutex *mutex;//互斥变量
    SDL_cond *cond;//条件变量
} PacketQueue;

typedef struct VideoPicture{
    SDL_Surface *surface;
    int width, height; /* source height & width */
    int allocated;//标记是否为surface申请了空间
}VideoPicture;

typedef struct VideoState{
    AVFormatContext *pFormatCtx;//文件格式上下文指针
    int videoStream, audioStream;//音视频流索引号
    AVStream *audio_st;//音频流
    PacketQueue audioQ;//音频包队列
    uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE*3)/2];//音频缓存区
    uint8_t *audio_buf_ptr;
    unsigned int audio_buf_size;//解码后的音频帧大小
    unsigned int audio_buf_index;//已输出音频数据大小
    AVPacket audio_pkt;//保存一个音频帧

    int audio_pkt_size;//音频数据包大小
    AVFrame *frame;//保存解码后的音频帧

    struct SwrContext *pAudioSwrCtx;//音频转换
    struct SwsContext *pVideoSwsCtx;//视频转换
    AVStream *video_st;//视频流
    PacketQueue videoQ;//视频队列

    VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];//解码后的视频图像
    int pictq_size,pictq_rindex,pictq_windex;//pictq大小以及读写索引

    SDL_mutex *pictq_mutex;//图像队列互斥变量
    SDL_cond *pictq_cond;//图像队列条件变量

    SDL_Thread *parse_tid;//解复用线程
    SDL_Thread *video_tid;//video解码线程

    char filename[512];//文件名
    int quit;//退出标志
}VideoState;

SDL_Window *gWindow;
SDL_Surface *gScreen;
AVFrame *gFrameRGB;
/* Since we only have one decoding thread, the Big Struct
   can be global in case we need it. */
VideoState *global_video_state;


const uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;//输出声道布局
const int out_nb_samples = 1024;
enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;//输出源采样格式
int out_nb_channels;//输出音频格式通道数
int out_buffer_size = 0;//输出源的缓存大小

int video_thread(void* ptr);
int parse_thread(void* ptr);
int stream_component_open(VideoState *is, int stream_index);
void do_exit_event(VideoState *is);
int queue_picture(VideoState *is, AVFrame *pFrame);
void alloc_picture(void *userdata);
static Uint32 sdl_refresh_timer_cb(Uint32 interval,void *opaque);
int decode_interrupt_cb(void *ptr);
static void schedule_refresh(VideoState *is, int delay);
void video_refresh_timer(void *userdata);
void video_display(VideoState *is);


void packet_queue_init(PacketQueue *pq)
{
    memset(pq,0,sizeof(PacketQueue));//清空结构体
    pq->mutex = SDL_CreateMutex();//创建一个互斥量
    pq->cond = SDL_CreateCond();//创建一个条件量
}
/**
first_pkt指针指向第一个数据包元素,每次更新last_pkt指针,指向最后一个数据包元素
**/
int packet_queue_put(PacketQueue *pq, AVPacket *pkt)
{
    AVPacketList *pktlist;
    if(av_dup_packet(pkt) < 0)
    {
        printf("dup packet error!\n");
        //将数据拷贝至私有缓存中
        return -1;
    }
    pktlist = av_malloc(sizeof(AVPacketList));
    if(!pktlist)
    {
        return -1;
    }
    pktlist->pkt = *pkt;//初始化列表元素
    pktlist->next = NULL;//初始化列表元素

    SDL_LockMutex(pq->mutex);//上锁
    if(!pq->last_pkt)
    {
        //如果last_pkt指针为空,则重新链接first_pkt指针至新建的pktlist元素
        pq->first_pkt = pktlist;
    }
    else
    {
        //如果last指针不为空,则将pktlist元素添加到last链表末端
        pq->last_pkt->next = pktlist;
    }
    pq->last_pkt = pktlist;//将last_pkt指针指向新添加的pktlist元素,移动last_pkt指针
    pq->nb_packets++;//数据包个数增加
    pq->size+=pktlist->pkt.size;//数据大小增加
    SDL_CondSignal(pq->cond);//通知条件变量
    SDL_UnlockMutex(pq->mutex);//解锁
    return 0;
}
int quit = 0;
/**
从队列中取出一个数据包block:标记是否以阻塞方式取数据包
**/
static int packet_queue_get(PacketQueue *pq, AVPacket *ptk, int block)
{
    AVPacketList *pktlist;
    int ret;
    SDL_LockMutex(pq->mutex);//上锁
    static bool flag = false;
    while(1)
    {
        if(quit)
        {
            ret = -1;
            break;
        }
        pktlist = pq->first_pkt;//从第一个数据包开始提取
        if(pktlist)
        {
            //如果该数据包存在的话
            pq->first_pkt = pktlist->next;//移动队列的first指针到下一个数据包
            if(!pq->first_pkt)
            {
                //如果first指针指向的数据包为空,则清除last数据包指针(说明队列里已经没有数据包了)
                pq->last_pkt = NULL;
            }
            pq->nb_packets --;//数据包个数自减
            pq->size -= pktlist->pkt.size;//数据大小自减
            *ptk = pktlist->pkt;//更新数据包
            av_free(pktlist);//释放数据包列表
            ret = 1;
            flag = true;
            break;
        }
        else if(!block)
        {
            //如果是非阻塞方式直接返回
            ret = 0;
            if(flag)
                ret = 2;//没有数据了
            break;
        }
        else
        {
            //如果是阻塞方式则进入睡眠模式等待唤醒
            SDL_CondWait(pq->cond,pq->mutex);//等待信号变量
        }
    }
    SDL_UnlockMutex(pq->mutex);//解锁
    return ret;
}
/*
解码一个音频数据包,返回解码的数据大小,一个音频包可能包含多个音频帧,但一次只解码一个音频帧,
所以一包音频可能需要多次才能解码完,使用while语句判断包数据是否全部解完,如果没有就解码当前包中的帧
,修改状态参数,否则,释放数据包,再从队列中取数据,记录初始值,再进循环。
*/
int audio_decode_frame(VideoState *is)
{
    AVPacket *pkt = &is->audio_pkt;//音频数据包,从队列中取出的数据包存放在这里

    int len1;
    int got_frame;
    int ret,result,out_sample_size;//输出的解码样品大小

    if(!is->frame)
    {
        is->frame = av_frame_alloc();//为frame申请空间
        if(!is->frame)
        {
            return -2;
        }
    }
    while(1)
    {
        while(pkt->size > 0)
        {
            //音频包数据还未解码完,继续执行解码操作
            out_sample_size = pkt->size;
            got_frame = 0;

            len1 = avcodec_decode_audio4(is->audio_st->codec,is->frame,&got_frame,pkt);//解码音频,从packet到frame中
            if(len1 < 0)
            {
                //如果有错误发生
                is->audio_pkt_size = 0;
                break;
            }

            if(got_frame>0)
            {
                //得到解码帧后执行重采样输出至out_buffer中
                ret = swr_convert(is->pAudioSwrCtx,&is->audio_buf_ptr,AVCODEC_MAX_AUDIO_FRAME_SIZE,
                                  (const uint8_t **)is->frame->data,is->frame->nb_samples);
                if(ret < 0)
                {
                    //转换失败
                    return -1;
                }

                out_sample_size = av_samples_get_buffer_size(NULL,out_nb_channels,ret,out_sample_fmt,1);
            }

            if(out_sample_size<=0)
                continue;
            is->audio_pkt.data += len1;//移动未解码数据指针
            is->audio_pkt.size -= len1;//更新未解码数据大小
            return out_sample_size;//解码完成返回解码的数据
        }
        if(pkt->data)
            av_free_packet(pkt);
        if(is->quit)
            return -1;
        result = packet_queue_get(&is->audioQ,pkt,1);
        if(result<0)//从队列中取出一个音频数据包 从这里展开!!!
            return -1;//读取失败
        else if(result == 2)
            return -2;
    }
}
void audio_callback(void *userdata, Uint8 *stream, int len)
{
    VideoState *is = (VideoState*)userdata;
    int len1, audio_size=0;
    is->audio_buf_ptr = is->audio_buf;
    while(len > 0)
    {
        if(is->audio_buf_index >= is->audio_buf_size)//音频已播放完需要更多的数据
        {
            audio_size = audio_decode_frame(is);//获取解码的数据大小,数据在frame中一个帧
            if(audio_size < 0)
            {
                //解码失败,则播放静音缓存区写0即可
                if(audio_size== -2)
                {
                    fprintf(stderr,"pause\n");
                    SDL_PauseAudio(1);
                    return;
                }

                is->audio_buf_size = 1024;
                memset(stream,0,is->audio_buf_size);

            }
            else
            {
                is->audio_buf_size = audio_size;//获取解码后的音频帧大小
            }
            is->audio_buf_index = 0;//刷新已输出音频数据大小
        }
        len1 = is->audio_buf_size - is->audio_buf_index;//获取需要播放本次解码音频帧的缓存区大小
        if(len1 > len)
            len1 = len;//如果帧大小比SDL缓存区还要大,则分割播放
        SDL_memcpy(stream,is->audio_buf_ptr + is->audio_buf_index,len1);
        len -= len1;
        stream += len1;
        is->audio_buf_index += len1;//更新已输出音频数据大小
    }
}

/*查找并打开相应的解码器*/
int stream_component_open(VideoState *is,int stream_index)
{
    AVFormatContext *pFormatCtx = is->pFormatCtx;//格式上下文
    AVCodecContext *codecCtx;//解码器上下文
    AVCodec *codec;//解码器
    SDL_AudioSpec wanted_spec,obtained_spec;//音频配置参数
    int64_t in_channel_layout;//输入源通道布局
    struct SwrContext *audio_swr_ctx;
    struct SwsContext *video_sws_ctx;
    int nb_bytes;
    uint8_t *buffer;
    int ret;
    if(stream_index < 0 || stream_index > pFormatCtx->nb_streams)
    {
        fprintf(stderr,"couldn't find the stream!\n");
        return -1;
    }
    codecCtx = pFormatCtx->streams[stream_index]->codec;
    if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO)
    {
        out_nb_channels = av_get_channel_layout_nb_channels(out_ch_layout);//获取输出音频的通道数
        wanted_spec.freq = codecCtx->sample_rate;//输出音频的采样率
        wanted_spec.format = AUDIO_S16SYS;//音频格式
        wanted_spec.channels = out_nb_channels;//输出源的通道数
        wanted_spec.silence = 0;
        wanted_spec.samples = out_nb_samples;//缓存区大小1024
        wanted_spec.callback = audio_callback;
        wanted_spec.userdata = is;

        if(SDL_OpenAudio(&wanted_spec,&obtained_spec) < 0)
        {
            fprintf(stderr,"SDL_OpenAudio Err:%s",SDL_GetError());
            return -1;
        }//打开音频设备

        in_channel_layout = av_get_default_channel_layout(codecCtx->channels);//获取音频原始通道布局
        audio_swr_ctx = swr_alloc();
        if(!audio_swr_ctx)
        {
            printf("could not allocate resampler context!\n");
            exit(1);
        }

        out_buffer_size = av_samples_get_buffer_size(NULL,out_nb_channels,out_nb_samples,out_sample_fmt,1);

        av_opt_set_int(audio_swr_ctx,"in_channel_layout",in_channel_layout,0);
        av_opt_set_int(audio_swr_ctx,"in_sample_rate",codecCtx->sample_rate,0);
        av_opt_set_sample_fmt(audio_swr_ctx,"in_sample_fmt",codecCtx->sample_fmt,0);

        av_opt_set_int(audio_swr_ctx,"out_channel_layout",out_ch_layout,0);
        av_opt_set_int(audio_swr_ctx,"out_sample_rate",codecCtx->sample_rate,0);
        av_opt_set_sample_fmt(audio_swr_ctx,"out_sample_fmt",out_sample_fmt,0);

        ret = swr_init(audio_swr_ctx);//初始化重采样结构体
        if(ret < 0)
        {
            printf("failed to init the resampling context!\n");
            exit(1);
        }
        is->pAudioSwrCtx = audio_swr_ctx;//更新到is结构体中
    }
    else if(codecCtx->codec_type == AVMEDIA_TYPE_VIDEO)
    {
        gFrameRGB = av_frame_alloc();
        if(!gFrameRGB)
        {
            return -1;
        }
        nb_bytes = avpicture_get_size(AV_PIX_FMT_RGB24,codecCtx->width,codecCtx->height);//转成rgb24需要的帧大小
        buffer = (uint8_t*)av_malloc(nb_bytes*sizeof(uint8_t));//为rgb24帧申请空间,buffer为起始地址

        avpicture_fill((AVPicture*)gFrameRGB,buffer,AV_PIX_FMT_RGB24,codecCtx->width,codecCtx->height);
        video_sws_ctx = sws_getContext(codecCtx->width,codecCtx->height,codecCtx->pix_fmt,
                                       codecCtx->width,codecCtx->height,AV_PIX_FMT_RGB24,
                                       SWS_BILINEAR,NULL,NULL,NULL);
        if(!video_sws_ctx)
        {
            return -1;
        }
        is->pVideoSwsCtx = video_sws_ctx;
    }
    codec = avcodec_find_decoder(codecCtx->codec_id);//查找解码器
    if(!codec || (avcodec_open2(codecCtx,codec,NULL)<0))//打开解码器
    {
        fprintf(stderr,"Unsupported codec!\n");
        return -1;
    }
    switch(codecCtx->codec_type)
    {
        case AVMEDIA_TYPE_AUDIO://音频
            is->audioStream = stream_index;//记录音频流索引
            is->audio_st = pFormatCtx->streams[stream_index];//记录音频流信息
            is->audio_buf_size = 0;//初始化解码音频数据大小
            is->audio_buf_index = 0;//初始化已播放音频数据大小
            is->audio_buf_ptr = is->audio_buf;
            memset(&is->audio_pkt,0,sizeof(is->audio_pkt));//清空音频数据包
            packet_queue_init(&is->audioQ);//初始化音频数据包队列
            SDL_PauseAudio(0);//开始播放音频,开始音频线程
            fprintf(stderr,"start playing audio\n");
            break;
        case AVMEDIA_TYPE_VIDEO://视频
            is->videoStream = stream_index;//记录视频流索引
            is->video_st = pFormatCtx->streams[stream_index];//记录视频流信息
            packet_queue_init(&is->videoQ);//初始化视频数据包队列
            is->video_tid = SDL_CreateThread(video_thread,"video_thread",is);//创建视频解码线程
            fprintf(stderr,"start playing video\n");
            break;
        default:
            break;
    }
    return 0;
}
void do_exit_event(VideoState *is)
{
    SDL_Event event;
    event.type = FF_QUIT_EVENT;//用户自定义事件
    event.user.data1 = is;
    SDL_PushEvent(&event);//将退出事件压入事件栈中
}
//解复用线程
int parse_thread(void* ptr)
{
    VideoState *is = (VideoState *)ptr;
    AVFormatContext *pFormatCtx;
    AVPacket pkt1,*packet=&pkt1;

    int video_index = -1;
    int audio_index = -1;
    int i;
    is->videoStream = -1;
    is->audioStream = -1;

    global_video_state = is;

    //open video file
    pFormatCtx = avformat_alloc_context();
    if(pFormatCtx == NULL)
    {
        fprintf(stderr,"couldn't alloc a avformatcontext\n");
        return -1;
    }

    //will interrupt blocking functions if we quit
    pFormatCtx->interrupt_callback.callback = decode_interrupt_cb;
    pFormatCtx->interrupt_callback.opaque = is;

    if(avformat_open_input(&pFormatCtx,is->filename,NULL, NULL) != 0)
    {
        printf("couldn't open file!\n");
        return -1;
    }
    is->pFormatCtx = pFormatCtx;

    //retrieve stream information
    if(avformat_find_stream_info(pFormatCtx,NULL) != 0)
    {
        fprintf(stderr,"couldn't find stream information!\n");
        return -1;
    }

    //dump information about file onto standard error
    av_dump_format(pFormatCtx,0,is->filename,0);//输出详细信息

    //一个文件一般有两种流:一个音频流、一个视频流
    for(i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0)
        {
            video_index = i;
        }
        if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0)
        {
            audio_index = i;
        }
    }

    if(audio_index >= 0)
    {
        //找到音频
        stream_component_open(is,audio_index);
    }
    if(video_index >= 0)
    {
        //找到视频
        stream_component_open(is,video_index);
    }

    if(is->videoStream < 0 || is->audioStream < 0)
    {
        fprintf(stderr,"%s: could not open codecs\n",is->filename);
        do_exit_event(is);
    }

    //main read loop
    while(1)
    {
        if(is->quit)
        {
            break;
        }
        //seek stuff goes here
        if(is->audioQ.size > MAX_AUDIOQ_SIZE ||
           is->videoQ.size > MAX_VIDEOQ_SIZE)
        {
            //队列已满则稍延时等待
            SDL_Delay(10);
            continue;
        }
        if(av_read_frame(is->pFormatCtx,packet)<0)
        {
            if(is->pFormatCtx->pb->error == 0)
            {
                //如果没有错误则继续执行
                SDL_Delay(100);
                continue;
            }
            else
            {
                break;
            }

        }
        //is this a packet from the video stream
        if(packet->stream_index == is->videoStream)
        {
            packet_queue_put(&is->videoQ,packet);
        }
        else if(packet->stream_index == is->audioStream)
        {
            packet_queue_put(&is->audioQ,packet);
        }
        else
        {
            av_free_packet(packet);//释放掉数据包
        }
    }
    while(!is->quit)
    {
        //如果退出标志没有置1则等待
        SDL_Delay(100);
    }
    do_exit_event(is);//产生退出事件
    return 0;
}
/*
*视频解码线程,从视频数据包队列中读取一包数据解码调用
*queue_picture函数传输到picture queue中
*/
int video_thread(void* ptr)
{
    VideoState *is = (VideoState*)ptr;//获取到VideoState指针
    AVPacket pkt1,*packet = &pkt1;
    int frameFinished;
    AVFrame *pFrame;

    pFrame = av_frame_alloc();
    if(!pFrame)
    {
        return -1;
    }
    while(1)
    {
        if(packet_queue_get(&is->videoQ,packet,1) < 0)
        {
            //means we quit getting packets
            break;
        }
        //Decode video frame
        avcodec_decode_video2(is->video_st->codec, pFrame,&frameFinished, packet);//解码数据包
        //Did we got a video frame
        if(frameFinished)
        {
            //传送到pictq队列里
            if(queue_picture(is,pFrame) < 0)
            {
                break;
            }
        }
        av_free_packet(packet);
    }
    av_frame_free(&pFrame);
    return 0;
}
int decode_interrupt_cb(void *ptr)
{
    return (global_video_state && global_video_state->quit);
}
/*存储视频帧到pictq队列中*/
int queue_picture(VideoState *is, AVFrame *pFrame)
{
    VideoPicture *vp;
    //int dst_pixel_fmt;

    /* wait util we have space for a new pic*/
    SDL_LockMutex(is->pictq_mutex);
    while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
          !is->quit)
    {
        //缓存区满时则等待
        SDL_CondWait(is->pictq_cond,is->pictq_mutex);
    }
    SDL_UnlockMutex(is->pictq_mutex);
    if(is->quit)
    {
        return -1;
    }
    //windex is set to 0 initially
    vp = &is->pictq[is->pictq_windex];//获取一个视频帧图像地址

    /*allocate or resize the buffer!*/
    /*如果surface没有效或者window窗口大小有改变*/
    if(!vp->surface ||
       vp->width != is->video_st->codec->width ||
       vp->height != is->video_st->codec->height)
    {
        SDL_Event event;
        vp->allocated = 0;
        /*we have to do it in the main thread*/
        event.type = FF_ALLOC_EVENT;
        event.user.data1 = is;
        SDL_PushEvent(&event);//压入申请图像空间事件

        /*wait util we have a picture allocated*/
        SDL_LockMutex(is->pictq_mutex);
        while(!vp->allocated && !is->quit)
        {
            //没有申请到空间且不退出则等待
            SDL_CondWait(is->pictq_cond,is->pictq_mutex);
        }
        SDL_UnlockMutex(is->pictq_mutex);
        if(is->quit)
        {
            return -1;
        }
    }
    /*allocate a frame if we need it*/
    /*we have a place to put our picture on the queue*/
    if(vp->surface)
    {
        //convert the image into rgb format that SDL uses
        sws_scale(is->pVideoSwsCtx,
                          (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, is->video_st->codec->height,
                          gFrameRGB->data, gFrameRGB->linesize);
        vp->surface->pixels = gFrameRGB->data[0];
        /*now we inform our display thread that we have a pic ready*/
        if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE)
        {
            is->pictq_windex = 0;
        }
        SDL_LockMutex(is->pictq_mutex);
        is->pictq_size++;
        SDL_UnlockMutex(is->pictq_mutex);
    }
    return 0;
}
void alloc_picture(void *userdata)
{
    VideoState *is = (VideoState*)userdata;
    VideoPicture *vp;

    vp = &is->pictq[is->pictq_windex];//获取到视频图像
    if(vp->surface)
    {
        //we already have one make another, bigger/smaller
        SDL_FreeSurface(vp->surface);
    }
    //allocate a place to put ouer rgb image on that screen
    vp->surface = SDL_CreateRGBSurface(0,is->video_st->codec->width,
                                       is->video_st->codec->height,
                                       24,rmask,gmask,bmask,amask);
    vp->width = is->video_st->codec->width;
    vp->height = is->video_st->codec->height;
    SDL_LockMutex(is->pictq_mutex);
    vp->allocated = 1;
    SDL_CondSignal(is->pictq_cond);
    SDL_UnlockMutex(is->pictq_mutex);
}
/*schedule a video refresh in 'delay' ms*/
static void schedule_refresh(VideoState *is, int delay)
{
    SDL_AddTimer(delay,sdl_refresh_timer_cb,is);
}
/*压入一个刷新视频图像事件*/
static Uint32 sdl_refresh_timer_cb(Uint32 interval,void *param)
{
    SDL_Event event;
    event.type = FF_REFRESH_EVENT;
    event.user.data1 = param;
    SDL_PushEvent(&event);
    return 0;/*0 means stop timer*/
}
/*图像刷新函数*/
void video_refresh_timer(void *userdata)
{
    VideoState *is = (VideoState*)userdata;
    VideoPicture *vp;

    if(is->video_st)
    {
        if(is->pictq_size == 0)
        {
            //缓存区无图像则再次刷新
            schedule_refresh(is,1);
        }
        else
        {
            vp = &is->pictq[is->pictq_rindex];//取出一个视频图像
            /* Now, normally here goes a ton of code
            about timing, etc. we're just going to
            guess at a delay for now. You can
            increase and decrease this value and hard code
            the timing - but I don't suggest that ;)
            We'll learn how to do it for real later.
            */

            schedule_refresh(is,80);

            /*show the picture!*/
            video_display(is);

            /*update queue for next picture!*/
            if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE)
            {
                is->pictq_rindex = 0;
            }
            SDL_LockMutex(is->pictq_mutex);
            is->pictq_size--;//图像队列大小减小
            SDL_CondSignal(is->pictq_cond);
            SDL_UnlockMutex(is->pictq_mutex);
        }
    }
    else
    {
        schedule_refresh(is,100);
    }
}
/*
*显示图像
*/
void video_display(VideoState *is)
{
    VideoPicture *vp;
    //AVPicture pict;
    //float aspect_ratio;//屏幕纵横比
    vp = &is->pictq[is->pictq_rindex];
    if(vp->surface)
    {
        #if 0
        if(is->video_st->codec->sample_aspect_ratio.num == 0)
        {
            aspect_ratio = 0;
        }
        else
        {
            aspect_ratio = av_q2d(is->video_st->codec->sample_aspect_ratio) *
                is->video_st->codec->width / is->video_st->codec->height;
        }
        if(aspect_ratio <= 0.0)
        {
            aspect_ratio = (float)is->video_st->codec->width /
                (float)is->video_st->codec->height;
        }
        #endif
        SDL_SetWindowSize(gWindow,vp->width,vp->height);
        gScreen = SDL_GetWindowSurface(gWindow);
        SDL_BlitSurface(vp->surface,NULL,gScreen,NULL);
        SDL_UpdateWindowSurface(gWindow);
    }
}
void event_loop(VideoState *is)
{
    SDL_Event event;
    while(1)
    {
        SDL_WaitEvent(&event);
        switch(event.type)
        {
            case SDL_QUIT:
            case FF_QUIT_EVENT:
                is->quit = 1;
                SDL_Quit();
                return ;
                break;
            case FF_ALLOC_EVENT:
                alloc_picture(event.user.data1);
                break;
            case FF_REFRESH_EVENT:
                video_refresh_timer(event.user.data1);
                break;
            default:
                break;
        }
    }
    return;
}
int main(int argc, char* args[])
{
    VideoState *is;
    is = av_mallocz(sizeof(VideoState));

    if(argc < 2)
    {
        fprintf(stderr,"Usage: test <file>/n");
        exit(1);
    }
    //register all formats and codecs
    av_register_all();

    if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER) < 0)
    {
        fprintf(stderr,"SDL init failed error:%s\n",SDL_GetError());
        exit(1);
    }
    gWindow = SDL_CreateWindow( "XPlayer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE );
    if(gWindow == NULL)
    {
        fprintf(stderr,"create window failed:%s",SDL_GetError());
        return -1;
    }
    gScreen = SDL_GetWindowSurface(gWindow);
    if(gScreen == NULL)
    {
        fprintf(stderr,"create surface failed:%s",SDL_GetError());
        return -1;
    }
    SDL_FillRect(gScreen,NULL,SDL_MapRGB(gScreen->format,0xff,0xff,0xff));//填充白色
    SDL_UpdateWindowSurface(gWindow);//初始化SDL Window窗口为白色状态

    av_strlcpy(is->filename,args[1],sizeof(is->filename));//拷贝文件名
    is->pictq_mutex = SDL_CreateMutex();//创建互斥变量
    is->pictq_cond = SDL_CreateCond();//创建条件变量

    schedule_refresh(is,40);//40ms后压入一个FF_REFRESH_EVENT事件

    is->parse_tid = SDL_CreateThread(parse_thread,"parse_thread",is);//创建解复用线程
    if(!is->parse_tid)
    {
        av_free(is);
        return -1;
    }
    event_loop(is);//进入事件循环中,其他线程同时在运行


#if 0

    // Free the RGB image
    av_free(buffer);
    av_free(pFrameRGB);
    swr_free(&audio_swr_ctx);
    sws_freeContext(pSwsCtx);
    SDL_FreeSurface(surface);
    surface = NULL;
    SDL_DestroyWindow(gWindow);
    gWindow = NULL;

    // Free the YUV frame
    av_free(pFrame);

    // Close the codec
    avcodec_close(pCodecCtx);
    avcodec_close(aCodecCtx);
    // Close the video file
    avformat_close_input(&pFormatCtx);
#endif
    return 0;
}

参考链接:http://dranger.com/ffmpeg/tutorial04.html

工程代码链接:http://download.csdn.net/download/key123zhangxing/8042711

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
ffmpeg SDL Qt播放器框架图主要包括三个核心组件:FFmpegSDL和Qt。 在框架图中,FFmpeg作为主要的多媒体处理库,负责解码、编码、转码和处理音视频数据。它支持多种音视频格式,并提供了丰富的API供开发者调用。FFmpeg可以从本地文件、网络流媒体或实时摄像头中获取音视频数据,并将其解码成原始音视频流。 SDL(Simple DirectMedia Layer)是一个跨平台的音视频输出和输入库,它提供了对音视频设备和图形处理的底层访问接口。SDL可以与各种操作系统、硬件和图形API相结合使用,并且具有卓越的性能和兼容性。在播放器框架中,SDL用于接收FFmpeg解码后的音视频数据,并将其输出到屏幕上进行显示。 Qt是一个功能强大的跨平台应用程序开发框架,提供了丰富的图形界面和多媒体功能。在播放器框架中,Qt用于创建播放器应用程序的用户界面,包括播放控制按钮、进度条、音量调节等交互元素。通过Qt,用户可以方便地操作播放器,选择不同的音视频文件进行播放,并进行暂停、停止、快进、快退等操作。 整个框架的工作流程如下:首先,通过Qt创建播放器应用程序的界面,并将其与SDL相关联。当用户通过界面选择要播放的音视频文件时,Qt利用FFmpeg从文件中读取音视频数据并进行解码。解码后的数据经过SDL输出到屏幕上进行实时显示。同时,Qt还负责管理播放器的状态,包括播放、暂停、停止和音量等。用户通过界面上的交互元素进行操作后,Qt会响应相应的事件,修改播放器状态,并与FFmpegSDL进行交互,实现音视频数据的读取、解码和输出。 通过整合FFmpegSDL和Qt,可以构建一个功能完善、易于使用的音视频播放器框架,实现强大的多媒体处理和播放功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值