前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >av_dump_format经验分析,FFmpeg获取媒体文件总时长(FLV获取总时长的误区)

av_dump_format经验分析,FFmpeg获取媒体文件总时长(FLV获取总时长的误区)

作者头像
手撕代码八百里
发布2024-05-24 13:14:08
640
发布2024-05-24 13:14:08
举报
文章被收录于专栏:猿计划猿计划

播放器有个功能,当用户打开视频时,需要读取媒体文件的总时长等信息,不巧的时,获取FLV时总失败,下面来具体分析下FLV和MP4获取总时长的原因和区别:

播放器有个获取MediaInfo的接口,功能如下:

代码语言:javascript
复制
int MediaFFmpeg::DecoderGetMediaInfo(MediaInfo *mi,AVCodecContext *decodec_ctx,AVStream *stream){
    if(!mi || !stream || !decodec_ctx){
        return -1;

    }

    //video
    if(stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
        //获取视频总时长
        if(AV_NOPTS_VALUE != stream->duration)
        {
            mi->duration =  stream->duration * av_q2d(stream->time_base);
            std::cout << "video_time : " <<
                    (mi->duration / 3600)<< ":" <<
                    (mi->duration % 3600) / 60<< ":" <<
                    (mi->duration % 60) << std::endl;

            char formatStr[128] = {0,};
                    sprintf(formatStr, "%02d:%02d:%02d",
                    (mi->duration / 3600),
                    ((mi->duration % 3600) / 60),
                    (mi->duration % 60));
            mi->durationFormatStr = formatStr;
        }
        else{
            printf("audio duration unknown ! \n");
        }

        mi->width = stream->codecpar->width;
        mi->height = stream->codecpar->height;
    }
    //audio
    else if(stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
        mi->sample_fmt                  =  AV_SAMPLE_FMT_S16;
        mi->sample_rate                 =   decodec_ctx->sample_rate;  //采样率/*48000; */
        mi->channels                    =   decodec_ctx->channels;     //通道数/*1;  */
        mi->nb_samples                  =   decodec_ctx->frame_size;/*1024;  */
        mi->audio_buffer_size           =   av_samples_get_buffer_size(NULL, mi->channels, mi->nb_samples, (enum AVSampleFormat)mi->sample_fmt, 1);   //输出buff
    }

    return 0;
}

有经验的人可能很快就能看出来是否存在问题。

总是打印duration不合法:

很奇怪的是,使用av_dump_format函数可以看到Duration:

代码语言:javascript
复制
Input #0, flv, from '/home/zhenghui/视频/1080P.flv':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    description     : Packed by Bilibili XCoder v2.0.2
    encoder         : Lavf60.3.100
  Duration: 00:03:46.53, start: 0.000000, bitrate: 3309 kb/s
  Stream #0:0: Video: flv1, yuv420p, 1920x1080, 200 kb/s, 30 fps, 30 tbr, 1k tbn
  Stream #0:1: Audio: adpcm_swf, 44100 Hz, stereo, s16, 352 kb/s

就翻了翻ffmpeg的源码,找到了av_dump_format的源码:

代码语言:javascript
复制
void av_dump_format(AVFormatContext *ic, int index,
                    const char *url, int is_output)
{
    int i;
    uint8_t *printed = ic->nb_streams ? av_mallocz(ic->nb_streams) : NULL;
    if (ic->nb_streams && !printed)
        return;

    av_log(NULL, AV_LOG_INFO, "%s #%d, %s, %s '%s':\n",
           is_output ? "Output" : "Input",
           index,
           is_output ? ic->oformat->name : ic->iformat->name,
           is_output ? "to" : "from", url);
    dump_metadata(NULL, ic->metadata, "  ");

    if (!is_output) {
        av_log(NULL, AV_LOG_INFO, "  Duration: ");
        if (ic->duration != AV_NOPTS_VALUE) {
            int64_t hours, mins, secs, us;
            int64_t duration = ic->duration + (ic->duration <= INT64_MAX - 5000 ? 5000 : 0);
            secs  = duration / AV_TIME_BASE;
            us    = duration % AV_TIME_BASE;
            mins  = secs / 60;
            secs %= 60;
            hours = mins / 60;
            mins %= 60;
            av_log(NULL, AV_LOG_INFO, "%02"PRId64":%02"PRId64":%02"PRId64".%02"PRId64"", hours, mins, secs,
                   (100 * us) / AV_TIME_BASE);
        } else {
            av_log(NULL, AV_LOG_INFO, "N/A");
        }
        if (ic->start_time != AV_NOPTS_VALUE) {
            int secs, us;
            av_log(NULL, AV_LOG_INFO, ", start: ");
            secs = llabs(ic->start_time / AV_TIME_BASE);
            us   = llabs(ic->start_time % AV_TIME_BASE);
            av_log(NULL, AV_LOG_INFO, "%s%d.%06d",
                   ic->start_time >= 0 ? "" : "-",
                   secs,
                   (int) av_rescale(us, 1000000, AV_TIME_BASE));
        }
        av_log(NULL, AV_LOG_INFO, ", bitrate: ");
        if (ic->bit_rate)
            av_log(NULL, AV_LOG_INFO, "%"PRId64" kb/s", ic->bit_rate / 1000);
        else
            av_log(NULL, AV_LOG_INFO, "N/A");
        av_log(NULL, AV_LOG_INFO, "\n");
    }

    if (ic->nb_chapters)
        av_log(NULL, AV_LOG_INFO, "  Chapters:\n");
    for (i = 0; i < ic->nb_chapters; i++) {
        const AVChapter *ch = ic->chapters[i];
        av_log(NULL, AV_LOG_INFO, "    Chapter #%d:%d: ", index, i);
        av_log(NULL, AV_LOG_INFO,
               "start %f, ", ch->start * av_q2d(ch->time_base));
        av_log(NULL, AV_LOG_INFO,
               "end %f\n", ch->end * av_q2d(ch->time_base));

        dump_metadata(NULL, ch->metadata, "      ");
    }

    if (ic->nb_programs) {
        int j, k, total = 0;
        for (j = 0; j < ic->nb_programs; j++) {
            const AVProgram *program = ic->programs[j];
            const AVDictionaryEntry *name = av_dict_get(program->metadata,
                                                        "name", NULL, 0);
            av_log(NULL, AV_LOG_INFO, "  Program %d %s\n", program->id,
                   name ? name->value : "");
            dump_metadata(NULL, program->metadata, "    ");
            for (k = 0; k < program->nb_stream_indexes; k++) {
                dump_stream_format(ic, program->stream_index[k],
                                   index, is_output);
                printed[program->stream_index[k]] = 1;
            }
            total += program->nb_stream_indexes;
        }
        if (total < ic->nb_streams)
            av_log(NULL, AV_LOG_INFO, "  No Program\n");
    }

    for (i = 0; i < ic->nb_streams; i++)
        if (!printed[i])
            dump_stream_format(ic, i, index, is_output);

    av_free(printed);
}

av_dump_format函数中使用的是AVFormatContext中的duration,而我使用的是AVStream的duration。

Debug了一下:AVFormatContext中的duration确实存在:

在这里插入图片描述
在这里插入图片描述

继续跟踪到AVStream的调用位置,确实不存在:

最终修改如下得已解决:

代码语言:javascript
复制
int MediaFFmpeg::DecoderGetMediaInfo(MediaInfo *mi,AVFormatContext *ic,AVCodecContext *decodec_ctx,AVStream *stream){
    if(!mi || !stream || !decodec_ctx){
        return -1;

    }

    //video
    if(stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
        //获取视频总时长
        if(AV_NOPTS_VALUE != ic->duration)
        {

            int64_t hours, mins, secs, us;
                       int64_t duration = ic->duration + (ic->duration <= INT64_MAX - 5000 ? 5000 : 0);
                       secs  = duration / AV_TIME_BASE;
                       us    = duration % AV_TIME_BASE;
                       mins  = secs / 60;
                       secs %= 60;
                       hours = mins / 60;
                       mins %= 60;


             mi->duration = duration;

             char formatStr[128] = {0,};
                               sprintf(formatStr, "%02ld:%02ld:%02ld",
                               hours,
                               mins,
                               secs);
//            mi->duration =  ic->duration * av_q2d(stream->time_base);
//            std::cout << "video_time : " <<
//                    (mi->duration / 3600)<< ":" <<
//                    (mi->duration % 3600) / 60<< ":" <<
//                    (mi->duration % 60) << std::endl;

//            char formatStr[128] = {0,};
//                    sprintf(formatStr, "%02d:%02d:%02d",
//                    (mi->duration / 3600),
//                    ((mi->duration % 3600) / 60),
//                    (mi->duration % 60));

            std::cout << "video_time : " << formatStr;
            mi->durationFormatStr = formatStr;
        }
        else{
            printf("audio duration unknown ! \n");
        }

        mi->width = stream->codecpar->width;
        mi->height = stream->codecpar->height;
    }
 	...
 	...
 	...

    return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档