ffplay源码__音频解码线程audio_thread

前言

audio_thread线程主要负责解码音频数据。本文是把CONFIG_AVFILTER设置为0去掉滤镜功能后分析的。

一、audio_thread线程

去掉滤镜后的代码如下:

static int audio_thread(void *arg)
{
    VideoState *is = arg;
    AVFrame *frame = av_frame_alloc();
    Frame *af;
    int got_frame = 0;
    AVRational tb;
    int ret = 0;

    if (!frame)
        return AVERROR(ENOMEM);

    do {
        if ((got_frame = decoder_decode_frame(&is->auddec, frame, NULL)) < 0)
            goto the_end;

        if (got_frame) {
                tb = (AVRational){1, frame->sample_rate};
             
                if (!(af = frame_queue_peek_writable(&is->sampq)))
                    goto the_end;

                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:
    av_frame_free(&frame);
    return ret;
}

在audio_thread线程中,主要是do{}while()循环。从以上代码看,主要函数是解码函数decoder_decode_frame,以及操作队列的函数,frame_queue_peek_writable()和frame_queue_push()。代码功能可以分为两部分。第一部分:解码音频数据。第二部分:把解码的数据插入队列。

二、解码函数decoder_decode_frame()

decoder_decode_frame()用于解码,返回1代表解码成功,返回0代表读取到文件末尾,返回负数代表解码错误。详见链接

ffplay源码分析__解码函数decoder_decode_frame-CSDN博客

三、解码数据插入音频FrameQueue

1、首先判断队列是否可写

用frame_queue_peek_writable()获取可写数组的指针

static 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_queue_peek_writable()返回NULL代表不可写,直接跳转到the_end标签,并释放AVFrame的内存。

如果队列满了没有可写空间,程序会一直阻塞在SDL_CondWait()中

如果有可写空间,返回可写数组的指针,准备向队列插入数据。

2、插入数据

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->pts代表该帧的显示时间戳,单位是时间基表示的,用av_q2d函数把frame->pts的时间戳单位转换成秒。以采样率为44100为例,时间基为AVRational{1,44100},每一份代表1/44100秒。

frame->pkt_pos是该frame在文件中的字节位置

is->auddec.pkt_serial是音频Decoder的序列号,在video_thread线程分析过序列号。把音频Decoder的序列号赋值给af->serial。

音频Decoder的序列号是在decoder_decode_frame()被赋值的,取自音频MyAVPacketList的序列号。所以此时Frame的序列号,Decoder的序列号,音频MyAVPacketList的序列号,以及音频PacketQueue的序列号都为1。

af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate});

音频的一帧数据中,一般每个通道包含1024个样本,af->duration代表一帧音频需要播放的时长为1024/44100=0.0232秒

接下来通过av_frame_move_ref(),拷贝frame的信息到af->frame中,至此Frame的赋值操作已完成。  然后通过frame_queue_push()更新队列的写索引和队列大小。 frame_queue_push()代码如下:

static 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);
    SDL_UnlockMutex(f->mutex);
}

通过frame_queue_push()更新队列的写索引和队列大小。 

四、总结

audio_thread主要功能是从音频PacketQueue中读取数据并解码,然后把解码后的数据插入到音频FrameQueue中。

audio_thread和video_thread基本流程一致,都用到解码函数decoder_decode_frame(),队列函数frame_queue_peek_writable()和frame_queue_push()。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值