23 ffmpeg 如何探测编解码器

1 avformat_find_stream_info

avformat_find_stream_info 是现在为止我看到的ffmpeg中最复杂的代码,我都分析过几回啦。每次都忘记,没关系,忘记了就接着来。

这个代码最主要的作用就是:确定每个stream的codec。那问题就是如何确定呢?

  1. 先判断是否已经具有完整的codec信息了。
  2. 读取frame
  3. try decode

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)
{
    int i, count, ret, read_size, j;
    AVStream *st;
    AVPacket pkt1, *pkt;
    int orig_nb_streams = ic->nb_streams;

首先判断stream具备完善codec信息。

    count = 0;
    read_size = 0;
    for(;;) {
        /* check if one codec still needs to be handled */
        for(i=0;inb_streams;i++) {
            int fps_analyze_framecount = 20;

            st = ic->streams[i];
            if (!has_codec_parameters(st->codec)) {
                av_log(ic, AV_LOG_DEBUG,
                       "avformat_find_stream_info no codec parameters,ctx_flags:%x,i:%d,read_size:%d\n",
                       ic->ctx_flags,i,read_size);
                break;
            }
        }

如果每个stream都具备,则break

        if (i == ic->nb_streams) {
            ret = count;
            av_log(ic, AV_LOG_DEBUG, "All info found %d,ctx_flags:%x,i:%d,read_size:%d\n",
                       count,ic->ctx_flags,i,read_size);
            break;
        }

读取frame

        ret = av_read_frame_internal(ic, &pkt1);
        pkt= add_to_pktbuf(&ic->packet_buffer, &pkt1, &ic->packet_buffer_end);        
        read_size += pkt->size;

decode frame

        st = ic->streams[pkt->stream_index];
        /* if still no information, we try to open the codec and to
           decompress the frame. We try to avoid that in most cases as
           it takes longer and uses more memory. For MPEG-4, we need to
           decompress for QuickTime. */
        if (!has_codec_parameters(st->codec) || !has_decode_delay_been_guessed(st))
            try_decode_frame(st, pkt, (options && i < orig_nb_streams )? &options[i] : NULL);

        st->codec_info_nb_frames++;
        count++;
    }

收尾功能

    // close codecs which were opened in try_decode_frame()
    for(i=0;inb_streams;i++) {
        st = ic->streams[i];
        if(st->codec->codec)
            avcodec_close(st->codec);
    }

    for (i=0; i < ic->nb_streams; i++)
        av_freep(&ic->streams[i]->info);
    return ret;
}

这个代码很复杂,我根据我自己的测试把很多不相关的代码删除了,对于其他场景可能有不同的流程和判断逻辑。简化代码的意思是我先弄明白这个函数主要功能。

2 try_decode_frame

读出一个帧后,需要试着去解码,来晚上编解码信息。


//st:需要解码的stream指针
//avpkt:需要解码的一个完整帧的数据
static int try_decode_frame(AVStream *st, AVPacket *avpkt, AVDictionary **options)
{
    int16_t *samples;
    AVCodec *codec;
    int got_picture, data_size, ret=0;
    AVFrame picture;

根据codec_id来find decoder and open

    if(!st->codec->codec){
        codec = avcodec_find_decoder(st->codec->codec_id);
        if (!codec)
            return -1;
        av_log(NULL, AV_LOG_DEBUG,
               "try_decode_frame run avcodec_find_decoder,code name:%s,codecid:%d\n"
                ,codec->name,st->codec->codec_id);
        ret = avcodec_open2(st->codec, codec, options);
        if (ret < 0)
            return ret;
    }

找到了codec,开始decode。调用的是正规的decode函数

    if(!has_codec_parameters(st->codec) || !has_decode_delay_been_guessed(st)){
        switch(st->codec->codec_type) {
        case AVMEDIA_TYPE_VIDEO:
            avcodec_get_frame_defaults(&picture);
            ret = avcodec_decode_video2(st->codec, &picture,
                                        &got_picture, avpkt);
            break;
        case AVMEDIA_TYPE_AUDIO:
            data_size = FFMAX(avpkt->size, AVCODEC_MAX_AUDIO_FRAME_SIZE);
            samples = av_malloc(data_size);
            if (!samples)
                goto fail;
            ret = avcodec_decode_audio3(st->codec, samples,
                                        &data_size, avpkt);
            av_free(samples);
            break;
        default:
            break;
        }
    }
 fail:
    return ret;
}

问题来了: codec_id是在try decode之前就找到的,它是如何找到这个codec_id的?是在读取数据的函数av_read_frame_internal中觉得。

3 av_read_frame_internal

av_read_frame_internal是读取一个完整的帧,调用了 av_read_packet。av_read_packet是获取数据,但不一定是完整的帧,这个函数中有一个行代码:

int av_read_packet(AVFormatContext *s, AVPacket *pkt){

    int ret, i;
    AVStream *st;

    for(;;){
        。。。
        ret= s->iformat->read_packet(s, pkt);
        。。。
    }
}

在s->iformat->read_packet设置codec_id。也就是在input formta中判断出了codecid。rtmp和flv的协议中的报文存储了编解码id。

你可能感兴趣的:(ffmpeg)