FFmpeg: 自实现ijkplayer播放器--08视频解码线程设计

视频解码

视频解码,读取数据包(packet),生成数据帧(frame),放入数据帧队列,用来输出音频和视频

解码流程

stream_component_open:

  1. 分配解码器上下文
    avcodec_alloc_context3
  2. 将码流中的解码信息拷贝到上下文
    avcodec_parameters_to_context
  3. 查找解码器
    avcodec_find_decoder
  4. 关联上下文和解码器
    avcodec_open2
  5. 音频解码:设置参数(采样率,声道数,布局通道等),获取steam索引和指针,初始化解码类Decoder,启动解码线程
  6. 视频解码:音频解码线程:获取steam索引和指针,初始化解码类Decoder, 启动解码线程
  7. 关闭上下文
int FFPlayer::stream_component_open(int stream_index)
{
    AVCodecContext *avctx;
    AVCodec *codec;
    int sample_rate;
    int nb_channels;
    int64_t channel_layout;
    int ret = 0;

    // 判断stream_index是否合法
    if (stream_index < 0 || stream_index >= ic->nb_streams)
        return -1;
    /*  为解码器分配一个编解码器上下文结构体 */
    avctx = avcodec_alloc_context3(NULL);
    if (!avctx)
        return AVERROR(ENOMEM);

    /* 将码流中的编解码器信息拷贝到新分配的编解码器上下文结构体 */
    ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);
    if (ret < 0)
        goto fail;
    // 设置pkt_timebase
    avctx->pkt_timebase = ic->streams[stream_index]->time_base;

    /* 根据codec_id查找解码器 */
    codec = avcodec_find_decoder(avctx->codec_id);
    if (!codec) {
         av_log(NULL, AV_LOG_WARNING,
                                      "No decoder could be found for codec %s\n", avcodec_get_name(avctx->codec_id));
        ret = AVERROR(EINVAL);
        goto fail;
    }
    if ((ret = avcodec_open2(avctx, codec, NULL)) < 0) {
        goto fail;
    }
    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        //从avctx(即AVCodecContext)中获取音频格式参数
        sample_rate = avctx->sample_rate;;  // 采样率
        nb_channels = avctx->channels;;    // 通道数
        channel_layout= avctx->channel_layout;; // 通道布局

        /* prepare audio output 准备音频输出*/
        //调用audio_open打开sdl音频输出,实际打开的设备参数保存在audio_tgt,返回值表示输出设备的缓冲区大小
        if ((ret = audio_open( channel_layout, nb_channels, sample_rate, &audio_tgt)) < 0)
            goto fail;
        audio_hw_buf_size = ret;
        audio_src = audio_tgt;  //暂且将数据源参数等同于目标输出参数
        //初始化audio_buf相关参数
        audio_buf_size  = 0;
        audio_buf_index = 0;

        audio_stream = stream_index;    // 获取audio的stream索引
        audio_st = ic->streams[stream_index];  // 获取audio的stream指针

         // 初始化ffplay封装的音频解码器, 并将解码器上下文 avctx和Decoder绑定
        auddec.decoder_init(avctx, &audioq);
        // 启动音频解码线程
        auddec.decoder_start(AVMEDIA_TYPE_AUDIO, "audio_thread", this);
        // 允许音频输出
        //play audio
        SDL_PauseAudio(0);
        break;
    case AVMEDIA_TYPE_VIDEO:
        video_stream = stream_index;    // 获取video的stream索引
        video_st = ic->streams[stream_index];// 获取video的stream指针
//        // 初始化ffplay封装的视频解码器
       viddec.decoder_init(avctx, &videoq); //  is->continue_read_thread
//        // 启动视频频解码线程
        if ((ret = viddec.decoder_start(AVMEDIA_TYPE_VIDEO, "video_decoder", this)) < 0)
            goto out;
        break;
    default:
        break;
    }

    goto out;
fail:
    avcodec_free_context(&avctx);

out:
    return ret;
}
视频解码线程

video_thread:

  1. 分配解码帧
    av_frame_alloc
  2. 获取stream timebase,帧率
  3. 循环读取视频解码帧 decoder_decode_frame : 先读取一个数据帧avcodec_receive_frame,若无数据帧,先从数据包队列取数据packet_queue_get,之后发送数据avcodec_send_packet,同时释放包
  4. 将解码后的视频帧插入队列
    queue_picture
  5. 释放frame对应的数据
    av_frame_unref
int Decoder::video_thread(void *arg)
{
     std::cout << __FUNCTION__ <<  " into " << std::endl;
     FFPlayer *is = (FFPlayer *)arg;
     AVFrame *frame = av_frame_alloc();  // 分配解码帧
     double pts;                 // pts
     double duration;            // 帧持续时间
     int ret;
     //1 获取stream timebase
     AVRational tb = is->video_st->time_base; // 获取stream timebase
     //2 获取帧率,以便计算每帧picture的duration
     AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);

     if (!frame)
         return AVERROR(ENOMEM);

     for (;;) {  // 循环取出视频解码的帧数据
         // 3 获取解码后的视频帧
         ret = get_video_frame(frame);
         if (ret < 0)
             goto the_end;   //解码结束, 什么时候会结束
         if (!ret)           //没有解码得到画面, 什么情况下会得不到解后的帧
             continue;
//           1/25 = 0.04秒
         // 4 计算帧持续时间和换算pts值为秒
         // 1/帧率 = duration 单位秒, 没有帧率时则设置为0, 有帧率帧计算出帧间隔
         duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
         // 根据AVStream timebase计算出pts值, 单位为秒
         pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);  // 单位为秒
         // 5 将解码后的视频帧插入队列
         ret = queue_picture(&is->pictq, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial_);
         // 6 释放frame对应的数据
         av_frame_unref(frame);

         if (ret < 0) // 返回值小于0则退出线程
             goto the_end;
     }
 the_end:
     std::cout << __FUNCTION__ <<  " leave " << std::endl;
     av_frame_free(&frame);
     return 0;
}
int Decoder::get_video_frame(AVFrame *frame)
{
    int got_picture;
    // 1. 获取解码后的视频帧
    if ((got_picture = decoder_decode_frame(frame)) < 0) {
        return -1; // 返回-1意味着要退出解码线程, 所以要分析decoder_decode_frame什么情况下返回-1
    }
    if (got_picture) {
        // 2. 分析获取到的该帧是否要drop掉, 该机制的目的是在放入帧队列前先drop掉过时的视频帧
//        frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);
    }

    return got_picture;
}
int Decoder::decoder_decode_frame(AVFrame *frame)
{
    int ret = AVERROR(EAGAIN);

    for (;;) {
        AVPacket pkt;
        do {  // 第一个循环 先把codec里的frame 全部读取

            if (queue_->abort_request)      // decoder_abort调用的时候 触发queue_->abort_request为1
                return -1;  // 是否请求退出
            switch (avctx_->codec_type) {
            case AVMEDIA_TYPE_VIDEO:
                ret = avcodec_receive_frame(avctx_, frame);
                //printf("frame pts:%ld, dts:%ld\n", frame->pts, frame->pkt_dts);
                if (ret >= 0) {
//                    if (decoder_reorder_pts == -1) {
//                        frame->pts = frame->best_effort_timestamp;
//                    } else if (!decoder_reorder_pts) {
//                        frame->pts = frame->pkt_dts;
//                    }
                } else {

                    char errStr[256] = { 0 };
                    av_strerror(ret, errStr, sizeof(errStr));
                    printf("video dec:%s\n", errStr);
                }
                break;
            case AVMEDIA_TYPE_AUDIO:
                ret = avcodec_receive_frame(avctx_, frame);
                if (ret >= 0) {
                    AVRational tb = (AVRational){1, frame->sample_rate};    //
                    if (frame->pts != AV_NOPTS_VALUE) {
                        // 如果frame->pts正常则先将其从pkt_timebase转成{1, frame->sample_rate}
                        // pkt_timebase实质就是stream->time_base
                        frame->pts = av_rescale_q(frame->pts, avctx_->pkt_timebase, tb);
                    }
//                    else if (d->next_pts != AV_NOPTS_VALUE) {
//                        // 如果frame->pts不正常则使用上一帧更新的next_pts和next_pts_tb
//                        // 转成{1, frame->sample_rate}
//                        frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);
//                    }
                }else {

                    char errStr[256] = { 0 };
                    av_strerror(ret, errStr, sizeof(errStr));
                    printf("audio dec:%s, ret:%d,%d\n", errStr, ret, AVERROR(EAGAIN));
                }
                break;
            }
            // 1.3. 检查解码是否已经结束,解码结束返回0
            if (ret == AVERROR_EOF) {
                printf("avcodec_flush_buffers %s(%d)\n", __FUNCTION__, __LINE__);
                avcodec_flush_buffers(avctx_);
                return 0;
            }
            // 1.4. 正常解码返回1
            if (ret >= 0)
                return 1;       // 获取到一帧frame
        }while (ret != AVERROR(EAGAIN));   // 1.5 没帧可读时ret返回EAGIN,需要继续送packet

        //  在目前这个版本我们还不去检测播放序列的问题
        // 2 如果上面的循环获取到了frame这里不会被执行,第二个循环,主要是读取packet送给解码器
//        do { //  在目前这个版本我们还不去检测播放序列的问题

//        if (queue_->nb_packets == 0)  // 没有数据可读
//            SDL_CondSignal(d->empty_queue_cond);// 通知read_thread放入packet

        // 2.3 阻塞式读取packet
        if (packet_queue_get(queue_, &pkt, 1, &pkt_serial_) < 0)
            return -1;

//   } while (d->queue->serial != d->pkt_serial);// 如果不是同一播放序列(流不连续)则继续读取

        if (avcodec_send_packet(avctx_, &pkt) == AVERROR(EAGAIN)) {
            av_log(avctx_, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
            // 先暂存这个pkt
        }
        av_packet_unref(&pkt);	// 一定要自己去释放音视频数据
    }
}
音频解码线程

audio_thread:

  1. 分配解码帧
    av_frame_alloc
  2. 读取解码帧: decoder_decode_frame,
  3. 获取可写帧,转换帧的时间戳,帧长 ,将解码帧frame赋值给可写帧
    frame_queue_peek_writable
    frame_queue_peek_writable
  4. 将可写帧放入帧队列
    frame_queue_push
  5. 释放解码帧
    av_frame_free
int Decoder::audio_thread(void *arg)
{
    std::cout << __FUNCTION__ <<  " into " << std::endl;
    FFPlayer *is = (FFPlayer *)arg;
    AVFrame *frame = av_frame_alloc();  // 分配解码帧
    Frame *af;
    int got_frame = 0;  // 是否读取到帧
    AVRational tb;      // timebase
    int ret = 0;

    if (!frame)
        return AVERROR(ENOMEM);

    do {
        // 1. 读取解码帧
        if ((got_frame = decoder_decode_frame(frame)) < 0)   // 是否获取到一帧数据
            goto the_end; // < =0 abort

        if (got_frame) {
            tb = (AVRational){1, frame->sample_rate};   // 设置为sample_rate为timebase

            // 2. 获取可写Frame
            if (!(af = frame_queue_peek_writable(&is->sampq)))  // 获取可写帧
                goto the_end;
            // 3. 设置Frame并放入FrameQueue
            af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);  // 转换时间戳
//            af->pos = frame->pkt_pos;
//            af->serial = is->auddec.pkt_serial;
            af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate});

            av_frame_move_ref(af->frame, frame);
            frame_queue_push(&is->sampq);  // 代表队列真正插入一帧数据

        }
    } while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF);
the_end:

    std::cout << __FUNCTION__ <<  " leave " << std::endl;
    av_frame_free(&frame);
    return ret;
}

数据帧队列

类型定义

用于缓存解码后的数据:
typedef struct Frame {
AVFrame *frame; // 指向数据帧
double pts; // 时间戳,单位为秒
double duration; // 该帧持续时间,单位为秒
int width; // 图像宽度
int height; // 图像高读
int format; // 对于图像为(enum AVPixelFormat)
} Frame;

帧队列:
typedef struct FrameQueue {
Frame queue[FRAME_QUEUE_SIZE]; // FRAME_QUEUE_SIZE 最大size, 数字太大时会占用大量的内存,需要注意该值的设置
int rindex; // 读索引。待播放时读取此帧进行播放,播放后此帧成为上一帧
int windex; // 写索引
int size; // 当前总帧数
int max_size; // 可存储最大帧数
SDL_mutex *mutex; // 互斥量
SDL_cond *cond; // 条件变量
PacketQueue *pktq; // 数据包缓冲队列
} FrameQueue;

数据帧队列api
  • 初始化FrameQueue
int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size)
{
    int i;
    memset(f, 0, sizeof(FrameQueue));
    if (!(f->mutex = SDL_CreateMutex())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    if (!(f->cond = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    f->pktq = pktq;
    f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE);

    for (i = 0; i < f->max_size; i++)
        if (!(f->queue[i].frame = av_frame_alloc())) // 分配AVFrame结构体
            return AVERROR(ENOMEM);
    return 0;
}
  • 获取帧
int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
{
    MyAVPacketList *pkt1;
    int ret;

    SDL_LockMutex(q->mutex);    // 加锁

    for (;;) {
        if (q->abort_request) {
            ret = -1;
            break;
        }

        pkt1 = q->first_pkt;    //MyAVPacketList *pkt1; 从队头拿数据
        if (pkt1) {     //队列中有数据
            q->first_pkt = pkt1->next;  //队头移到第二个节点
            if (!q->first_pkt)
                q->last_pkt = NULL;
            q->nb_packets--;    //节点数减1
            q->size -= pkt1->pkt.size + sizeof(*pkt1);  //cache大小扣除一个节点
            q->duration -= pkt1->pkt.duration;  //总时长扣除一个节点
            //返回AVPacket,这里发生一次AVPacket结构体拷贝,AVPacket的data只拷贝了指针
            *pkt = pkt1->pkt;
            if (serial) //如果需要输出serial,把serial输出
                *serial = pkt1->serial;
            av_free(pkt1);      //释放节点内存,只是释放节点,而不是释放AVPacket
            ret = 1;
            break;
        } else if (!block) {    //队列中没有数据,且非阻塞调用
            ret = 0;
            break;
        } else {    //队列中没有数据,且阻塞调用
            //这里没有break。for循环的另一个作用是在条件变量满足后重复上述代码取出节点
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);  // 释放锁
    return ret;
}
  • 更新写指针
// 更新写指针
void frame_queue_push(FrameQueue *f)
{
    if (++f->windex == f->max_size)
        f->windex = 0;
    SDL_LockMutex(f->mutex);
    f->size++;
    SDL_CondSignal(f->cond);    // 当_readable在等待时则可以唤醒
    SDL_UnlockMutex(f->mutex);
}
  • 获取可写指针
Frame *frame_queue_peek_writable(FrameQueue *f)
{
    /* wait until we have space to put a new frame */
    SDL_LockMutex(f->mutex);
    while (f->size >= f->max_size &&
           !f->pktq->abort_request) {	/* 检查是否需要退出 */
        SDL_CondWait(f->cond, f->mutex);
    }
    SDL_UnlockMutex(f->mutex);

    if (f->pktq->abort_request)			 /* 检查是不是要退出 */
        return NULL;

    return &f->queue[f->windex];
}
  • 获取可读
Frame *frame_queue_peek_readable(FrameQueue *f)
{
    /* wait until we have a readable a new frame */
    SDL_LockMutex(f->mutex);
    while (f->size <= 0 &&
           !f->pktq->abort_request) {
        SDL_CondWait(f->cond, f->mutex);
    }
    SDL_UnlockMutex(f->mutex);

    if (f->pktq->abort_request)
        return NULL;

    return &f->queue[(f->rindex) % f->max_size];
}
  • 更新写指针
void frame_queue_push(FrameQueue *f)
{
    if (++f->windex == f->max_size)
        f->windex = 0;
    SDL_LockMutex(f->mutex);
    f->size++;
    SDL_CondSignal(f->cond);    // 当_readable在等待时则可以唤醒
    SDL_UnlockMutex(f->mutex);
}
  • 释放当前frame,并更新读索引rinde
void frame_queue_next(FrameQueue *f)
{
    frame_queue_unref_item(&f->queue[f->rindex]);
    if (++f->rindex == f->max_size)
        f->rindex = 0;
    SDL_LockMutex(f->mutex);
    f->size--;
    SDL_CondSignal(f->cond);
    SDL_UnlockMutex(f->mutex);
}
  • 发送信号
void frame_queue_signal(FrameQueue *f)
{
    SDL_LockMutex(f->mutex);
    SDL_CondSignal(f->cond);
    SDL_UnlockMutex(f->mutex);
}
  • 销毁
void frame_queue_destory(FrameQueue *f)
{
    int i;
    for (i = 0; i < f->max_size; i++) {
        Frame *vp = &f->queue[i];
        // 释放对vp->frame中的数据缓冲区的引用,注意不是释放frame对象本身
        frame_queue_unref_item(vp);
        // 释放vp->frame对象
        av_frame_free(&vp->frame);
    }
    SDL_DestroyMutex(f->mutex);
    SDL_DestroyCond(f->cond);
}

相关推荐

  1. FFmpeg: 实现ijkplayer播放器--08视频解码线设计

    2024-04-14 07:08:01       38 阅读
  2. FFmpeg: 实现ijkplayer播放器--07解复用线设计

    2024-04-14 07:08:01       40 阅读
  3. FFmpeg:实现ijkplayer播放器--11音视频同步

    2024-04-14 07:08:01       34 阅读
  4. FFmpeg: 实现ijkplayer播放器-02环境搭建

    2024-04-14 07:08:01       41 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-14 07:08:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-14 07:08:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-14 07:08:01       87 阅读
  4. Python语言-面向对象

    2024-04-14 07:08:01       96 阅读

热门阅读

  1. Flume配置案例@Source:文件,Channel+Sink:Kafka

    2024-04-14 07:08:01       35 阅读
  2. Python学习入门(3)—— 高级技巧

    2024-04-14 07:08:01       41 阅读
  3. 删除url的search参数,避免页面刷新

    2024-04-14 07:08:01       37 阅读
  4. 天空盒1-天空盒的实现原理

    2024-04-14 07:08:01       34 阅读