FFMPEG无法显示视频的时长

9
我正在尝试使用ffmpeg从视频文件中捕获帧,但我甚至无法获得视频的持续时间。每次我尝试使用pFormatCtx->duration访问它时,都会得到0。我知道指针已初始化并包含正确的持续时间,因为如果我使用av_dump_format(pFormatCtx, 0, videoName, 0);,那么我实际上会得到视频的持续时间数据以及其他信息。当我使用av_dump_format(pFormatCtx, 0, videoName, 0);时,我得到的就是以下内容:
Input #0, avi, from 'futurama.avi':
Duration: 00:21:36.28, start: 0.000000, bitrate: 1135 kb/s
Stream #0.0: Video: mpeg4 (Advanced Simple Profile), yuv420p, 512x384
[PAR 1:1 DAR 4:3], 25 tbr, 25 tbn, 25 tbc
Stream #0.1: Audio: ac3, 48000 Hz, stereo, s16, 192 kb/s 

我不明白为什么av_dump_format可以显示持续时间,而我不能。我检查了函数定义,为了显示持续时间,该函数也使用了pFormatCtx->duration。当我在main.cpp中调用它们时,不仅是持续时间,其他成员变量也无法正确显示数据。
以下是我的代码:
extern "C" {
    #include<libavcodec/avcodec.h>
    #include<libavformat/avformat.h>
    #include<libswscale/swscale.h>
}

int main(int argc, char *argv[]) {
    AVFormatContext *pFormatCtx = NULL;

    const char videoName[] = "futurama.avi";

    // Register all formats and codecs.
    av_register_all();
    cout << "Opening the video file";
    // Open video file
    int ret = avformat_open_input(&pFormatCtx, videoName, NULL, NULL) != 0;
    if (ret != 0) {
        cout << "Couldn't open the video file." << ret ;
        return -1;
    }
    if(avformat_find_stream_info(pFormatCtx, 0) < 0) {
        cout << "problem with stream info";
        return -1;
    }

    av_dump_format(pFormatCtx, 0, videoName, 0);
    cout << pFormatCtx->bit_rate << endl; // different value each time, not initialized properly.
    cout << pFormatCtx->duration << endl; // 0
    return 0;
}

我不知道这是否有帮助,但我在Ubuntu上使用QtCreator,并静态链接了库。


1
av_dump_format在读取pFormatCtv->duration之前进行了某些操作,使该字段有效。换句话说,在持续时间变为有效之前必须执行其他代码。追踪一些可行的代码,你应该能找到缺少的部分。顺便问一下,您对此问题还感兴趣吗? - Randall Cook
我本来想在我的个人项目中使用ffmpeg的功能,但最后只能通过new processes与ffmpeg.exe一起使用。我想找到一个答案,毕竟ffmpeg是一个非常强大的工具,我相信我将来还会用到它,如果我知道如何使用库而不是在新进程中使用可执行文件,那将会更加高效。 - Malkavian
我可能暂时无法尝试你的方法,这些日子我非常忙,我会给你点赞的,如果它有效的话,我会让你知道的。再次感谢! - Malkavian
感谢您的点赞。关于使用库与可执行文件效率更高的问题,我并不确定。在我之前的工作中,我们几乎完全使用库,有时候会很困难。而在我现在的工作中,我们完全使用可执行文件,事情进展得更加顺利。我想这更符合Unix风格,将几个程序组合在一个shell脚本中以实现所需的结果,而不是围绕几个库编写自定义编译代码。 - Randall Cook
4个回答

9

duration属性的单位是time_base,而不是毫秒或秒。将其转换为毫秒非常容易,

double time_base =  (double)video_stream->time_base.num / (double)video_stream->time_base.den;
double duration = (double)video_stream->duration * time_base * 1000.0;

现在持续时间以毫秒为单位,只需取整数或向上取整即可获得整数毫秒数,任选一种方法。

3
请注意,在ffmpeg的头文件中甚至有一个内联函数可以进行转换:rational.h中的av_q2d - Sam

3
av_open_input_file()avformat_open_input()的区别可能是后者不读取流信息,因此duration未初始化。调用avformat_find_stream_info()解决了我的问题。
我使用了代码片段来计算/显示http://ffmpeg.org/doxygen/trunk/dump_8c_source.html#l00480(请注意,行号在更新版本中可能会发生变化)。并添加了一些初始化代码,“对我有用”。希望可以帮到您。
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>

int main()
{
    const char const* file = "sample.mpg";
    AVFormatContext* formatContext = NULL;

    av_register_all();

    // Open video file
    avformat_open_input(&formatContext, file, NULL, NULL);
    avformat_find_stream_info(formatContext, NULL);

    // Lower log level since av_log() prints at AV_LOG_ERROR by default
    av_log_set_level(AV_LOG_INFO);

    av_log(NULL, AV_LOG_INFO, "  Duration: ");
    if (formatContext->duration != AV_NOPTS_VALUE) {
        int hours, mins, secs, us;
        int64_t duration = formatContext->duration + 5000;
        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, "%02d:%02d:%02d.%02d\n", hours, mins, secs, (100 * us) / AV_TIME_BASE);
    } 

    return 0;
}

编译代码,

gcc -o duration -lavutil -lavformat duration.c

它显示了错误的视频持续时间,你有检查过吗?@Baris Demiray - axita.savani

2
如何从ffmpeg中获取持续时间信息(及更多)
我以前曾经尝试过使用ffmpeg,发现学习曲线相当陡峭。所以即使OP在几个月前提出了这个问题,我仍然会在这里发布一些代码,以防其他SO上的用户希望做类似的事情。下面的Open()函数是完整的,但有许多断言,并且缺乏适当的错误处理。
首先,我看到的一个立即区别是,我使用了av_open_input_file而不是avformat_open_input。 我也没有使用av_dump_format。
计算持续时间可能很棘手,特别是对于H.264和MPEG-2;请看如何计算下面的durationSec。
注意:此示例还使用JUCE C++实用程序库。
注意2:此代码是ffmpeg教程的修改版本。
void VideoCanvas::Open(const char* videoFileName)
{       
    Logger::writeToLog(String(L"Opening video file ") + videoFileName);
    Close();

    AVCodec *pCodec;

    // register all formats and codecs
    av_register_all();  

    // open video file
    int ret = av_open_input_file(&pFormatCtx, videoFileName, NULL, 0, NULL);
    if (ret != 0) {
        Logger::writeToLog("Unable to open video file: " + String(videoFileName));
        Close();
        return;
    }

    // Retrieve stream information
    ret = av_find_stream_info(pFormatCtx);
    jassert(ret >= 0);

    // Find the first video stream
    videoStream = -1;
    audioStream = -1;
    for(int i=0; i<pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO && videoStream < 0) {
            videoStream = i;            
        }
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audioStream < 0) {
            audioStream = i;
        }
    } // end for i
    jassert(videoStream != -1);
    jassert(audioStream != -1);

    // Get a pointer to the codec context for the video stream
    pCodecCtx=pFormatCtx->streams[videoStream]->codec;
    jassert(pCodecCtx != nullptr);

    /**
      * This is the fundamental unit of time (in seconds) in terms
      * of which frame timestamps are represented. For fixed-fps content,
      * timebase should be 1/framerate and timestamp increments should be
      * identically 1.
      * - encoding: MUST be set by user.
      * - decoding: Set by libavcodec.
      */
    AVRational avr = pCodecCtx->time_base;
    Logger::writeToLog("time_base = " + String(avr.num) + "/" + String(avr.den));

    /**
     * For some codecs, the time base is closer to the field rate than the frame rate.
     * Most notably, H.264 and MPEG-2 specify time_base as half of frame duration
     * if no telecine is used ...
     *
     * Set to time_base ticks per frame. Default 1, e.g., H.264/MPEG-2 set it to 2.
     */
    ticksPerFrame = pCodecCtx->ticks_per_frame;
    Logger::writeToLog("ticks_per_frame = " + String(pCodecCtx->ticks_per_frame));

    durationSec = static_cast<double>(pFormatCtx->streams[videoStream]->duration) * static_cast<double>(ticksPerFrame) / static_cast<double>(avr.den);
    double fH = durationSec / 3600.;
    int     H = static_cast<int>(fH);
    double fM = (fH - H) * 60.;
    int     M = static_cast<int>(fM);
    double fS = (fM - M) * 60.;
    int     S = static_cast<int>(fS);

    Logger::writeToLog("Video stream duration = " + String(H) + "H " + String(M) + "M " + String(fS, 3) + "S");

    // calculate frame rate based on time_base and ticks_per_frame
    frameRate = static_cast<double>(avr.den) / static_cast<double>(avr.num * pCodecCtx->ticks_per_frame);
    Logger::writeToLog("Frame rate = " + String(frameRate) );

    // audio codec context
    if (audioStream != -1) {
        aCodecCtx = pFormatCtx->streams[audioStream]->codec;

        Logger::writeToLog("Audio sample rate = " + String(aCodecCtx->sample_rate));
        Logger::writeToLog("Audio channels    = " + String(aCodecCtx->channels));       
    }
    jassert(aCodecCtx != nullptr);

    // format:
    // The "S" in "S16SYS" stands for "signed", the 16 says that each sample is 16 bits long, 
    // and "SYS" means that the endian-order will depend on the system you are on. This is the
    // format that avcodec_decode_audio2 will give us the audio in.

    // open the audio codec
    if (audioStream != -1) {
        aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
        if (!aCodec) {
            Logger::writeToLog(L"Unsupported codec ID = " + String(aCodecCtx->codec_id) );
            Close();
            return;  // TODO: should we just play video if audio codec doesn't work?
        }
        avcodec_open(aCodecCtx, aCodec);
    }


    // Find the decoder for the video stream
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
    if(pCodec == nullptr) {
        jassert(false);
        // fprintf(stderr, "Unsupported codec!\n");
        //return -1; // Codec not found
    }

    // Open video codec
    ret = avcodec_open(pCodecCtx, pCodec);
    jassert(ret >= 0);

    // Allocate video frame
    pFrame=avcodec_alloc_frame();
    jassert(pFrame != nullptr);

    // Allocate an AVFrame structure
    pFrameRGB=avcodec_alloc_frame();
    jassert(pFrameRGB != nullptr);

    int numBytes = avpicture_get_size(PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height);
    jassert(numBytes != 0);
    buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
    jassert(buffer != nullptr);

    // note: the pixel format here is RGB, but sws_getContext() needs to be PIX_FMT_BGR24 to match (BGR)
    // this might have to do w/ endian-ness....make sure this is platform independent
    if (m_image != nullptr) delete m_image;
    m_image = new Image(Image::ARGB, pCodecCtx->width, pCodecCtx->height, true);

    int dstW = pCodecCtx->width; // don't rescale
    int dstH = pCodecCtx->height;
    Logger::writeToLog(L"Video width = " + String(dstW));
    Logger::writeToLog(L"Video height = " + String(dstH));

    // this should only have to be done once
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, dstW, dstH, PIX_FMT_RGB32, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    jassert(img_convert_ctx != nullptr);  

    setSize(pCodecCtx->width, pCodecCtx->height);

} // Open()

1
你可以从AVFormatContext中获取持续时间,但是格式上下文中的持续时间是以AV_TIME_BASE为单位的。
了解更多有关FFMPEG时间基础知识这里
avformat.h文档中得知:

/**
流的持续时间,以AV_TIME_BASE分数秒为单位。如果您不知道任何单个流的持续时间并且也没有设置任何单个流的持续时间,则仅设置此值。如果未设置,则从AVStream值推导出此值。 仅限解复用,由libavformat设置。
*/
int64_t duration;

因此,您应该使用av_q2d(AV_TIME_BASE_Q)将时间基转换为秒。
AVFormatContext *fmt_ctx;

/* init fmt_ctx etc. */

double duration_in_sec = (int) (fmt_ctx->duration * av_q2d(AV_TIME_BASE_Q));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接