ffmpeg源码分析2

来源:互联网 发布:ubuntu16.04 网络映射 编辑:程序博客网 时间:2024/06/05 14:22

正文

基本上上一篇讲解了ffplayer的main函数的流程,主要讲解了编解码器以及触发器的注册。今天我们看下如何解码文件。开始结合源码分析。

正文

stream_open这个货用来开启四个线程,专门处理视频流的。我们如果想完全了解清楚,就一点点的看。

static VideoState *stream_open(const char *filename, AVInputFormat *iformat){    VideoState *is;    is = av_mallocz(sizeof(VideoState));    ......//初始化了很多东西,队列啦,变量啦等等。    is->read_tid     = SDL_CreateThread(read_thread, "read_thread", is);//这是最核心的开启一个read_thread,通过这个启动解码线程和循环读取文件。    ......    return is;

这里逻辑很简单,就是初始化一个类型为VideoState 的结构体,其实这里很容易理解,因为c语言是面向过程的,所以最好有个全局的数据结构,可以方便区分逻辑,其实我们可以吧这个东西看成一个我们最关心的核心类,所有操作解码都是通过VideoState 来的,我们可以看下这个结构体

typedef struct VideoState {    SDL_Thread *read_tid;  //读取的线程号    AVInputFormat *iformat; //    ......    AVFormatContext *ic; //这是一个从头文件中读取到的视频信息的一个结构体    ......    FrameQueue pictq; //解析后的帧数据    FrameQueue subpq;    FrameQueue sampq;    //解码器    Decoder auddec;    Decoder viddec;    Decoder subdec;//从文件读取到的每一帧的未经解析的数据。    PacketQueue audioq;    enum ShowMode {        SHOW_MODE_NONE = -1, SHOW_MODE_VIDEO = 0, SHOW_MODE_WAVES, SHOW_MODE_RDFT, SHOW_MODE_NB    } show_mode; //不是核心,   //控制显示的testure    SDL_Texture *vis_texture;    SDL_Texture *sub_texture;    SDL_Texture *vid_texture;    AVStream *subtitle_st;    PacketQueue subtitleq;    AVStream *video_st;    PacketQueue videoq;    char *filename;    int width, height, xleft, ytop;    int last_video_stream, last_audio_stream, last_subtitle_stream;    SDL_cond *continue_read_thread;} VideoState;

这个结构体,基本包含了所有的内容,但是我不太清楚他问啥不作为一个静态变量存在,仅仅是作为一个局部变量。一直通过指针来传递,并且解析视频,貌似不支持同事播放多个视频的,所以基本没啥意义的。

static int read_thread(void *arg){    VideoState *is = arg;    AVFormatContext *ic = NULL;    ic = avformat_alloc_context();    err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {        ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);    }    for (;;) {    ......//这里机制很复杂,大概是如果读取一定数量的包后,就不在读取了,这里暂时不管        ret = av_read_frame(ic, pkt);            packet_queue_put(&is->audioq, pkt);    }}

前者半部分打开了三个线程用来解析字幕,视频音频,并且通过指令,解析到最适合的流,但是那里的代码,不是核心,并且比较复杂,就不在详细介绍,音频和字幕的解码,其实原理一样。都是调用一个函数,暂时不去关心。后半部分的一个无限循环,是读取packet,然后加入特定的对列,让解码线程解码。我们去看下到底如何解析视频的。

static int stream_component_open(VideoState *is, int stream_index){    avctx = avcodec_alloc_context3(NULL);    ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);//获取解码的上下文,也就是    codec = avcodec_find_decoder(avctx->codec_id);  //获取解码器。        decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);        if ((ret = decoder_start(&is->viddec, video_thread, is)) < 0)            goto out;        is->queue_attachments_req = 1;        break;}

这里的代码是开始解码前的准备工作,上一步已经读取到文件的未经解析的packet文件,加入到一个对列,这里是解码钱的准备工作。通过video_thread开启一个线程,可以完全解码。这里我们稍微浏览下

static int video_thread(void *arg){    for (;;) {        ret = get_video_frame(is, frame);      ......//不是核心,假如到一个队列中,用来渲染}
static int get_video_frame(VideoState *is, AVFrame *frame){    int got_picture;    if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0)        return -1;}
static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) {    int ret = AVERROR(EAGAIN);    for (;;) {        AVPacket pkt;        if (d->queue->serial == d->pkt_serial) {            do {                if (d->queue->abort_request)                    return -1;                switch (d->avctx->codec_type) {                    case AVMEDIA_TYPE_VIDEO:                        ret = avcodec_receive_frame(d->avctx, frame);                        if (ret >= 0) {                            if (decoder_reorder_pts == -1) {                                frame->pts = frame->best_effort_timestamp;                            } else if (!decoder_reorder_pts) {                                frame->pts = frame->pkt_dts;                            }                        }                        break;                    case AVMEDIA_TYPE_AUDIO:                        ret = avcodec_receive_frame(d->avctx, frame);                        if (ret >= 0) {                            AVRational tb = (AVRational){1, frame->sample_rate};                            if (frame->pts != AV_NOPTS_VALUE)                                frame->pts = av_rescale_q(frame->pts, av_codec_get_pkt_timebase(d->avctx), tb);                            else if (d->next_pts != AV_NOPTS_VALUE)                                frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);                            if (frame->pts != AV_NOPTS_VALUE) {                                d->next_pts = frame->pts + frame->nb_samples;                                d->next_pts_tb = tb;                            }                        }                        break;                }                if (ret == AVERROR_EOF) {                    d->finished = d->pkt_serial;                    avcodec_flush_buffers(d->avctx);                    return 0;                }                if (ret >= 0)                    return 1;            } while (ret != AVERROR(EAGAIN));        }        do {            if (d->queue->nb_packets == 0)                SDL_CondSignal(d->empty_queue_cond);            if (d->packet_pending) {                av_packet_move_ref(&pkt, &d->pkt);                d->packet_pending = 0;            } else {                if (packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) < 0)                    return -1;            }        } while (d->queue->serial != d->pkt_serial);        if (pkt.data == flush_pkt.data) {            avcodec_flush_buffers(d->avctx);            d->finished = 0;            d->next_pts = d->start_pts;            d->next_pts_tb = d->start_pts_tb;        } else {            if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) {                int got_frame = 0;                ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, &pkt);                if (ret < 0) {                    ret = AVERROR(EAGAIN);                } else {                    if (got_frame && !pkt.data) {                       d->packet_pending = 1;                       av_packet_move_ref(&d->pkt, &pkt);                    }                    ret = got_frame ? 0 : (pkt.data ? AVERROR(EAGAIN) : AVERROR_EOF);                }            } else {                if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) {                    av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");                    d->packet_pending = 1;                    av_packet_move_ref(&d->pkt, &pkt);                }            }            av_packet_unref(&pkt);        }    }}

解码完成,懒得解析了,有空大家看,一篇完整的视频编解码流程是:
相对简单的ffmpeg解码流程

后记

关于SDL的渲染,我没看。也懒得解释了,就不在详细介绍解码关键几个函数等有空再翻翻源码,争取搞懂。

原创粉丝点击