ijkplayer proceso de decodificación de video de análisis de código fuente

Prefacio

Este artículo es la quinta parte del análisis del proceso, analizando el proceso de decodificación de video en ijkPlayer, en video_thread, como se muestra en el siguiente diagrama de flujo.
Inserte la descripción de la imagen aquí

Estructura IJKFF_Pipenode

Definido en ff_ffpipenode.hy ff_ffpipenode.c

ffpipenode significa decodificador de video, que encapsula soluciones blandas y duras.

// 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);
  • Proceso de inicialización de pipenode:
    llame a pipeline.ffpipeline_open_video_decoder en stream_component_open para crear

proceso de llamada video_thread

La operación de decodificación de cuadros de video está en el hilo video_thread. El video_thread lee el paquete de video de packet_queue, y después de la decodificación suave / rígida, lo coloca en frame_queue a través de queue_picture.

// 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
};

// 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 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")
}

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

// 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);
}

Cómo se decodifican los fotogramas de video y cómo se ponen en cola

Se distingue por llamar en video_thread ffpipenode_run_sync. La decodificación de video se divide en solución blanda y solución difícil; aquí hay una pasada rápida y luego un análisis detallado.
La realización de la solución blanda es ffpipenode_ffplay_vdec.c, y
la realización de la solución dura es ffpipenode_android_mediacodec_vdec.c.
Después de la decodificación, ambos serán transferidos a ff_ffplay.c # queue_picture para unirse al equipo.

Escribiré un análisis de artículo después de la solución blanda y la solución difícil, pero el código es fácil de entender, el problema no es grande y el proceso de solución blanda es similar a la decodificación de audio.

// 软解,ffpipenode_ffplay_vdec.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“存入”该节点
    }    
}

Cómo se lee y procesa el fotograma de video video_refresh_thread

Se crea un video_refresh_thread en el método stream_open, en el que el cuadro de video se lee de frame_queue para la sincronización de audio y video y luego se procesa.
Ignore la sincronización de audio y video aquí y hable sobre el proceso de renderizado directamente.

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);
    }
}

Referencia:
análisis del marco de decodificación ijkplayer análisis de
realización de decodificación ijkplayer-solución dura
análisis de realización de decodificación ijkplayer-solución suave

Supongo que te gusta

Origin blog.csdn.net/u014099894/article/details/112970636
Recomendado
Clasificación