ffmpeg源码分析2

正文

基本上上一篇讲解了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的渲染,我没看。也懒得解释了,就不在详细介绍解码关键几个函数等有空再翻翻源码,争取搞懂。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值