美文网首页
ijkplayer学习笔记(七)——音频解码和输出

ijkplayer学习笔记(七)——音频解码和输出

作者: 程序媛的程 | 来源:发表于2021-09-23 14:28 被阅读0次

    第五篇中的stream_component_open方法中,我们注意到音频解码线程的入口函数audio_thread

    音频解码

    audio_thread—> decoder_decode_frame —> avcodec_receive_frame

    static int audio_thread(void *arg)
    {
        ......
        do {
            ffp_audio_statistic_l(ffp);
            // 将packet中的数据送去解码,得到解码后的帧数据
            if ((got_frame = decoder_decode_frame(ffp, &is->auddec, frame, NULL)) < 0)
                goto the_end;
                ......
                while ((ret = av_buffersink_get_frame_flags(is->out_audio_filter, frame, 0)) >= 0) {
                    // 判断sampq是否能写,获取可写Frame,设置相应参数
                    if (!(af = frame_queue_peek_writable(&is->sampq)))
                        goto the_end;
                    ......
                    //调用av_frame_move_ref()移动AVFrame->buffer,添加到Frame(解码后)队列
                    av_frame_move_ref(af->frame, frame);
                   // 这个函数用来唤醒线程,当sampq为空时,音频播放线程会阻塞,这时候需要唤醒该线程
                    frame_queue_push(&is->sampq);
                    ......
            }
        } while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF);
        ......
    }
    
    static int decoder_decode_frame(FFPlayer *ffp, 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_AUDIO:
                            //在decoder_decode_frame方法中,通过循环调用avcodec_receive_frame方法来获取解码完成帧
                            ret = avcodec_receive_frame(d->avctx, frame);
                            ......
                        default:
                            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));
            }
            // 获取一个packet,如果播放序列不一致(数据不连续)则过滤掉“过时”的packet
            do {
    //如果没有数据可读则唤醒read_thread, 实际是continue_read_thread SDL_cond
                if (d->queue->nb_packets == 0) // 没有数据可读
                    SDL_CondSignal(d->empty_queue_cond);
    //如果还有pending的packet则使用它
                if (d->packet_pending) {
                    av_packet_move_ref(&pkt, &d->pkt);
                    d->packet_pending = 0;
                } else {
    //阻塞式读取packet
                    if (packet_queue_get_or_buffering(ffp, d->queue, &pkt, &d->pkt_serial, &d->finished) < 0)
                        return -1;
                }
            } while (d->queue->serial != d->pkt_serial);
            //获取的Packet判断是否是flush Packet
            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 {
                    //  调用avcodec_send_packet() 进行解码
                    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);
            }
        }
    }
    

    总结:
    1、audio_thread线程一开始就循环调用decoder_decode_frame()进行解码,解码后的帧存放到frame中,这期间,通过frame_queue_peek_writable()实时判断是否能把刚刚解码的frame写入is->sampq中(判断sampq队列是否满了),如果没位置放frame的话,会最终调用pthread_cond_wait()来阻塞队列。
    2、is->sampq是音频解码帧列表,播放线程直接从这里读取数据然后播放出来。

    音频播放

    说到播放,就要提起第三篇中提到的输出管线IJKFF_Pipeline结构体对象了。核心api是ffpipeline_create_from_ios,其中有一行:

    pipeline->func_open_audio_output = func_open_audio_output;

    func_open_audio_output被赋值

    static SDL_Aout *func_open_audio_output(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
    {
        // 主要完成的是创建SDL_Aout对象
        return SDL_AoutIos_CreateForAudioUnit();
    }
    

    之前了解过,stream_component_open打开解码器时,该方法里面也调用audio_open打开了audio output设备,audio_open主要任务是配置关于音频输出相关的参数,并通过SDL_AoutOpenAudio给AudioQueue且通过不断的callback来获取pcm数据进行播放。

    参考:https://blog.csdn.net/xipiaoyouzi/article/details/74280170

    相关文章

      网友评论

          本文标题:ijkplayer学习笔记(七)——音频解码和输出

          本文链接:https://www.haomeiwen.com/subject/epdggltx.html