ijkplayer源码分析 视频解码流程

本系列如下:

视频渲染流程
音频播放流程
read线程流程
音频解码流程
视频解码流程
视频向音频同步
start流程和buffering缓冲策略

本文是流程分析的第五篇,分析ijkPlayer中的视频解码流程,在video_thread中,如下流程图中所示。

IJKFF_Pipenode结构体

定义在 ff_ffpipenode.h 和 ff_ffpipenode.c

ffpipenode表示视频解码器,封装了软解和硬解。

// ff_ffpipenode.h
typedef struct IJKFF_Pipenode_Opaque IJKFF_Pipenode_Opaque;
typedef struct IJKFF_Pipenode IJKFF_Pipenode;
struct IJKFF_Pipenode {
    SDL_mutex *mutex;
    void *opaque;

    void (*func_destroy) (IJKFF_Pipenode *node);
    int  (*func_run_sync)(IJKFF_Pipenode *node);
    int  (*func_flush)   (IJKFF_Pipenode *node); // optional
};

IJKFF_Pipenode *ffpipenode_alloc(size_t opaque_size);
void ffpipenode_free(IJKFF_Pipenode *node);
void ffpipenode_free_p(IJKFF_Pipenode **node);

int  ffpipenode_run_sync(IJKFF_Pipenode *node);
int  ffpipenode_flush(IJKFF_Pipenode *node);

pipenode初始化流程:

在stream_component_open中调用pipeline.ffpipeline_open_video_decoder创建

video_thread调用流程

视频帧的解码操作是在video_thread线程中,video_thread从packet_queue中读取了视频packet,并软/硬解码后,通过queue_picture放入frame_queue中。

// ff_ffpipenode.h
typedef struct IJKFF_Pipenode IJKFF_Pipenode;
struct IJKFF_Pipenode {
    SDL_mutex *mutex;
    void *opaque;

    void (*func_destroy) (IJKFF_Pipenode *node);
    int  (*func_run_sync)(IJKFF_Pipenode *node);
    int  (*func_flush)   (IJKFF_Pipenode *node); // optional
};

// ff_ffplay.c
static int stream_component_open(FFPlayer *ffp, int stream_index) {
    decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);
    // 创建IJKFF_Pipenode,创建并初始化解码器,ffpipenode封装了硬/软解码器
    ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);
    decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec")
}

// ff_ffpipeline.c
IJKFF_Pipenode* ffpipeline_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{
    live_log(ffp->inject_opaque, NULL);
    return pipeline->func_open_video_decoder(pipeline, ffp);
}

// ffpipeline_android.c
static IJKFF_Pipenode *func_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{
    IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
    IJKFF_Pipenode        *node = NULL;

    if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2)
        // 硬解
        node = ffpipenode_create_video_decoder_from_android_mediacodec(ffp, pipeline, opaque->weak_vout);

    if (!node) {
        // 硬解创建失败走软解
        node = ffpipenode_create_video_decoder_from_ffplay(ffp);
    }

    return node;
}

// ff_ffplay.c
static int video_thread(void *arg) {
    ret = ffpipenode_run_sync(ffp->node_vdec); // 调用ffpipenode_run_sync
    return ret;
}

// ff_ffpipenode.c
int ffpipenode_run_sync(IJKFF_Pipenode *node) {
    return node->func_run_sync(node);
}

视频帧解码流程

在video_thread中调用ffpipenode_run_sync就区分开了,视频解码分为软解和硬解;这里迅速过一下,之后再详细分析。
软解的实现是,ffpipenode_ffplay_vdec.c,
硬解的实现是,ffpipenode_android_mediacodec_vdec.c,

软件流程会调到ff_ffplay.c#ffp_video_thread中;
硬件流程就在ffpipenode_android_mediacodec_vdec.c文件中,
这两种方式解码后都会会调到ff_ffplay.c#queue_picture进行入队,等待渲染。

/*
 * 软解流程,ffpipenode_ffplay_vdec.c
 */ 
static int func_run_sync(IJKFF_Pipenode *node)
{
    IJKFF_Pipenode_Opaque *opaque = node->opaque;
    return ffp_video_thread(opaque->ffp);
}

// ff_ffplay.c
static int ffplay_video_thread(void *arg) {
    AVFrame *frame = av_frame_alloc();
    for(;;){
        ret = get_video_frame(ffp, frame); // avcodec_receive_frame软解码获取一帧
        queue_picture(ffp, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);
    }
}

/*
 * 硬解,ffpipenode_android_mediacodec_vdec.c
 */
static int func_run_sync(IJKFF_Pipenode *node){
    int got_frame = 0;
    while (!q->abort_request) {
        drain_output_buffer(env, node, timeUs, &dequeue_count, frame, &got_frame);
        if (got_frame) {
            // 通过头文件调用到queue_picture
            ffp_queue_picture(ffp, frame, pts, duration, av_frame_get_pkt_pos(frame), is->viddec.pkt_serial);
        }
    }
}

// 入队 ff_ffplay.c
static int
queue_picture(FFPlayer *ffp, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial) {
    Frame *vp;
    vp = frame_queue_peek_writable(&is->pictq); // 获取一个可写节点
    if (!vp->bmp){
        alloc_picture(ffp, src_frame->format); // 创建bmp

        vp->allocated = 0;
        vp->width = src_frame->width;
        vp->height = src_frame->height;
        vp->format = src_frame->format;

    }
    if (vp->bmp) {
        SDL_VoutLockYUVOverlay(vp->bmp); // 锁
        SDL_VoutFillFrameYUVOverlay(vp->bmp, src_frame);  // 调用func_fill_frame把帧画面“绘制”到最终的显示图层上
        SDL_VoutUnlockYUVOverlay(vp->bmp);

        vp->pts = pts;
        vp->duration = duration;
        vp->pos = pos;
        vp->serial = serial;
        vp->sar = src_frame->sample_aspect_ratio;
        vp->bmp->sar_num = vp->sar.num;
        vp->bmp->sar_den = vp->sar.den;

        frame_queue_push(&is->pictq); // 对节点操作结束后,调用frame_queue_push告知FrameQueue“存入”该节点
    }    
}

视频帧渲染流程

stream_open方法中创建了video_refresh_thread,在该线程里从frame_queue读取视频帧进行音视频同步后进行渲染。
此处忽略音视频同步,直接讲渲染流程。

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat) {
    // 创建video_refresh_thread,单独线程进行视频渲染
    SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");
}

#define REFRESH_RATE 0.01

static int video_refresh_thread(void *arg) {
    FFPlayer *ffp = arg;
    VideoState *is = ffp->is;
    double remaining_time = 0.0;
    while (!is->abort_request) {
        if (remaining_time > 0.0) {
            // video_refresh里进行音视频同步,更改remaining_time的值,在此休息相应的时间
            av_usleep((int) (int64_t) (remaining_time * 1000000.0));
        }
        remaining_time = REFRESH_RATE;
        if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
            video_refresh(ffp, &remaining_time);
    }

    return 0;
}

static void video_refresh(FFPlayer *opaque, double *remaining_time) {
    if (!is->video_st) {
        return;
    }

    if (frame_queue_nb_remaining(&is->pictq) == 0) {
        // 队列里没有帧,do nothing
    } else {
        // ... 此处忽略音视频同步,直接渲染下一帧
        frame_queue_next(&is->pictq); // 移动到下一帧
        is->force_refresh = 1;
    }

        if (!ffp->display_disable
            && is->force_refresh
            && is->show_mode == SHOW_MODE_VIDEO
            && is->pictq.rindex_shown) {

            video_display2(ffp); // 显示
        }
}

static void video_display2(FFPlayer *ffp) {
    VideoState *is = ffp->is;
    if (is->video_st)
        video_image_display2(ffp);
}

static void video_image_display2(FFPlayer *ffp) {
    Frame *vp = frame_queue_peek_last(&is->pictq); // 就是要渲染的这一帧

    if (vp->bmp) {
        // 进行渲染
        SDL_VoutDisplayYUVOverlay(ffp->vout, vp->bmp);
    }
}

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓ 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于视频编解码的FPGA源码分析,这是一个非常复杂和广泛的话题。在FPGA中实现视频编解码通常涉及到以下几个方面: 1. 视频编码标准:FPGA实现视频编解码首先需要选择一个视频编码标准,如H.264、H.265、MPEG-2等。不同的标准有不同的压缩算法和编码器结构。 2. 编码器架构:视频编码器通常包括预处理、变换、量化、熵编码等模块。在FPGA中实现编码器,需要设计合适的数据流架构,使得各个模块能够高效地并行处理视频数据。 3. 解码器架构:视频解码器通常包括熵解码、逆量化、逆变换、去块效应滤波等模块。在FPGA中实现解码器,需要设计合适的数据流架构,使得各个模块能够高效地并行处理解码数据。 4. 缓存和存储:视频编解码通常需要使用大量的缓存和存储来存储视频数据和中间结果。在FPGA中实现视频编解码,需要合理设计和管理缓存和存储结构,以满足高带宽和低延迟的需求。 5. 性能优化:在FPGA中实现视频编解码,需要进行一系列的性能优化,如流水线设计、并行处理、资源共享等,以提高系统的吞吐量和效率。 综上所述,视频编解码的FPGA源码分析涉及到多个方面的知识和技术,需要深入理解视频编解码算法和FPGA架构,并且具备良好的硬件设计和优化能力。对于具体的视频编解码源码分析,您可以参考相关的文档、论文或者开源项目,以了解具体实现的细节和思路。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值