文档章节

JavaCV读取mov文件帧数为0

抢小孩糖吃
 抢小孩糖吃
发布于 2017/07/21 21:26
字数 1961
阅读 41
收藏 0
点赞 0
评论 0

编者注

之前一直被某大公司面试时说对各种原理不深入,在这里碰到一个OpenCV的问题,决定研究OpenCV源代码,在研究解决方案。

问题代码

在使用JavaCV的过程中,我在MAC电脑上重新编译了一遍OpenCV是可以正确读取到mov的视频帧数的,但是转换到linux系统上,重新OpenCV无法解决这个问题。linux上无法让opencv3.2编译时链接好最新的ffmpeg库

package com.hava.xxx.xxx.xxx.utils;

import static org.bytedeco.javacpp.opencv_videoio.*;

/**
 * Created by zhanpeng on 2017/2/23.
 */
public class JavaCV {

    public static double numFrames(String video_path){
        CvCapture cvCapture = cvCaptureFromFile(video_path);

        double numFrames = cvGetCaptureProperty(cvCapture,CV_CAP_PROP_FRAME_COUNT);

        return numFrames;
    }
}

通过之前对JavaCV的翻译,确认Java调用C的类库实现的操作。则最核心的问题还是出现在C的库当中,当然经过多方测试,在Linux上编译了OpenCV但是无法和ffmpeg相连接。决定开展对OpenCV的解刨。

JavaCV代码映射OpenCV代码

通过上面的代码,我们可以了解到通过cvCaptureFromFile函数对视频文件进行读取。通过cvGetCaptureProperty函数放入之前文件对象和对应参数,能够获取帧数。通过import引入,可以明确知道这些函数在opencv_videoio的包当中。

在OpenCV源代码中,进入build\opencv\opencv-3.2.0\modules\videoio\src\cap.cpp文件中,首先找到cvGetCaptureProperty函数,在opencv3.2源代码105-108行

CV_IMPL double cvGetCaptureProperty( CvCapture* capture, int id )
{
    return icvGetCaptureProperty(capture, id);
}

由此了解到操作的实质是通过icvGetCaptureProperty来实现的。

static inline double icvGetCaptureProperty( const CvCapture* capture, int id )
{
    return capture ? capture->getProperty(id) : 0;
}

通过icvGetCaptureProperty源代码可以看到,通过对capture对象的getProperty来获取对应参数,如果获取失败则返回0。
顺便复习了下C语言的条件运算符<表达式1>?<表达式2>:<表达式3>,首先判断表达式1,如果为真则执行表达式2,如果为假则执行表达式3。

OpenCV的实现调用问题

通过分析源代码,可以看到很多种类的实现,各个实现也都具有getProperty函数,则能够确认的是调用了某个实现的getProperty才能够获取序列帧数。虽然不清楚为什么JavaCV的函数调用为什么和Cpp版本的名称不一致,但是我还是找到了Cpp版本的实现。

CV_IMPL CvCapture * cvCreateFileCapture (const char * filename)
{
    return cvCreateFileCaptureWithPreference(filename, CV_CAP_ANY);
}

根据上面的代码,可以看到重点是cvCreateFileCaptureWithPreference,以下截取比较重要的内容部分

/**
 * Videoreader dispatching method: it tries to find the first
 * API that can access a given filename.
 */
CV_IMPL CvCapture * cvCreateFileCaptureWithPreference (const char * filename, int apiPreference)
{
    CvCapture * result = 0;

    switch(apiPreference) {
    default:
        // user specified an API we do not know
        // bail out to let the user know that it is not available
        if (apiPreference) break;

#ifdef HAVE_FFMPEG
    case CV_CAP_FFMPEG:
        TRY_OPEN(result, cvCreateFileCapture_FFMPEG_proxy (filename))
        if (apiPreference) break;
#endif

。。。。。。

#if defined(HAVE_QUICKTIME) || defined(HAVE_QTKIT)
    case CV_CAP_QT:
        TRY_OPEN(result, cvCreateFileCapture_QT (filename))
        if (apiPreference) break;
#endif

。。。。。。

    return result;
}

以上仅仅只截取了比较重要的部分,首先可以看到,如果具有ffmpeg的包,则opencv会优先调用ffmpeg,但是mac天生自带quicktime,在opencv的实现中也可能会调用quicktime的模块来实现帧数的调取,由于是Java调用Cpp的库,我当前无法对Cpp的库进行Debug,则我也无法弄清楚,在mac具体调用的是什么实现。但是我认为对ffmpeg和quicktime的opencv实现,对获取视频序列帧数是有极大的帮助。

OpenCV真实调用在哪里?

通过上面的结论加上,可以知道去查找cap_ffmpeg.cppcap_qt.cpp的源代码实现,但是会碰到新的问题。首先以ffmpeg为例子

    virtual double getProperty(int propId) const
    {
        return ffmpegCapture ? icvGetCaptureProperty_FFMPEG_p(ffmpegCapture, propId) : 0;
    }

通过getProperty的实现可以知道实现是由icvGetCaptureProperty_FFMPEG_p来完成的

static CvGetCaptureProperty_Plugin icvGetCaptureProperty_FFMPEG_p = 0;

icvGetCaptureProperty_FFMPEG_p = (CvGetCaptureProperty_Plugin)GetProcAddress(icvFFOpenCV, "cvGetCaptureProperty_FFMPEG");

icvGetCaptureProperty_FFMPEG_p = (CvGetCaptureProperty_Plugin)cvGetCaptureProperty_FFMPEG;

以上代码当中GetProcAddress函数检索指定的动态链接库(DLL)中的输出库函数地址。也就是说调用动态链接库cvGetCaptureProperty_FFMPEG,新的问题这个方法在哪里?应该存在于ffmpeg

cap_ffmpeg_impl.hpp

20170801
之前的问题困扰了很久,隔三差五的Google,但是就是没找到结果。神奇的向小伙伴抱怨找不到的时候看到了神奇的两个文件cap_ffmpeg_api.hppcap_ffmpeg_impl.hpp。impl难道是实现,为什么实现放到hpp里面,听小伙伴说hpp是头文件,头文件放什么实现@_@
在ffmpeg_api.hpp文件中找到了我之前一直没有找到的函数定义

OPENCV_FFMPEG_API double cvGetCaptureProperty_FFMPEG(struct CvCapture_FFMPEG* cap, int prop);
OPENCV_FFMPEG_API double cvGetCaptureProperty_FFMPEG_2(struct CvCapture_FFMPEG_2* cap, int prop);

在ffmpeg_impl.hpp中

double cvGetCaptureProperty_FFMPEG(CvCapture_FFMPEG* capture, int prop_id)
{
    return capture->getProperty(prop_id);
}

和如下实现

double CvCapture_FFMPEG::getProperty( int property_id ) const
{
    if( !video_st ) return 0;

    switch( property_id )
    {
    case CV_FFMPEG_CAP_PROP_POS_MSEC:
        return 1000.0*(double)frame_number/get_fps();
    case CV_FFMPEG_CAP_PROP_POS_FRAMES:
        return (double)frame_number;
    case CV_FFMPEG_CAP_PROP_POS_AVI_RATIO:
        return r2d(ic->streams[video_stream]->time_base);
    case CV_FFMPEG_CAP_PROP_FRAME_COUNT:
        return (double)get_total_frames();
    case CV_FFMPEG_CAP_PROP_FRAME_WIDTH:
        return (double)frame.width;
    case CV_FFMPEG_CAP_PROP_FRAME_HEIGHT:
        return (double)frame.height;
    case CV_FFMPEG_CAP_PROP_FPS:
        return get_fps();
    case CV_FFMPEG_CAP_PROP_FOURCC:
#if LIBAVFORMAT_BUILD > 4628
        return (double)video_st->codec->codec_tag;
#else
        return (double)video_st->codec.codec_tag;
#endif
    case CV_FFMPEG_CAP_PROP_SAR_NUM:
        return get_sample_aspect_ratio(ic->streams[video_stream]).num;
    case CV_FFMPEG_CAP_PROP_SAR_DEN:
        return get_sample_aspect_ratio(ic->streams[video_stream]).den;
    default:
        break;
    }

    return 0;
}

终于找到帧数的实现get_total_frames

int64_t CvCapture_FFMPEG::get_total_frames() const
{
    int64_t nbf = ic->streams[video_stream]->nb_frames;

    if (nbf == 0)
    {
        nbf = (int64_t)floor(get_duration_sec() * get_fps() + 0.5);
    }
    return nbf;
}

终于找到了使用ffmpeg实现的地方,分别查看get_duration_secget_fps函数,从上面代码我们知道首先尝试从流当中取总帧数,如果返回为0,则获取视频长度和帧速率+0.5,之后向下取整数。

double CvCapture_FFMPEG::get_duration_sec() const
{
    double sec = (double)ic->duration / (double)AV_TIME_BASE;

    if (sec < eps_zero)
    {
        sec = (double)ic->streams[video_stream]->duration * r2d(ic->streams[video_stream]->time_base);
    }

    if (sec < eps_zero)
    {
        sec = (double)ic->streams[video_stream]->duration * r2d(ic->streams[video_stream]->time_base);
    }

    return sec;
}

double CvCapture_FFMPEG::get_fps() const
{
#if 0 && LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(55, 1, 100) && LIBAVFORMAT_VERSION_MICRO >= 100
    double fps = r2d(av_guess_frame_rate(ic, ic->streams[video_stream], NULL));
#else
#if LIBAVCODEC_BUILD >= CALC_FFMPEG_VERSION(54, 1, 0)
    double fps = r2d(ic->streams[video_stream]->avg_frame_rate);
#else
    double fps = r2d(ic->streams[video_stream]->r_frame_rate);
#endif

#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(52, 111, 0)
    if (fps < eps_zero)
    {
        fps = r2d(ic->streams[video_stream]->avg_frame_rate);
    }
#endif

    if (fps < eps_zero)
    {
        fps = 1.0 / r2d(ic->streams[video_stream]->codec->time_base);
    }
#endif
    return fps;
}

根据上面那两个方法的实现,我们可以知道根本还是从ic对象的streams中获取各种参数内容。

ic指针对象的调用

下面这里把ic的关键调用罗列出来

bool CvCapture_FFMPEG::open( const char* _filename )
{
    unsigned i;
    bool valid = false;

    close();

#if USE_AV_INTERRUPT_CALLBACK
    /* interrupt callback */
    interrupt_metadata.timeout_after_ms = LIBAVFORMAT_INTERRUPT_OPEN_TIMEOUT_MS;
    get_monotonic_time(&interrupt_metadata.value);

    ic = avformat_alloc_context();
    ic->interrupt_callback.callback = _opencv_ffmpeg_interrupt_callback;
    ic->interrupt_callback.opaque = &interrupt_metadata;
#endif

#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(52, 111, 0)
    av_dict_set(&dict, "rtsp_transport", "tcp", 0);
    int err = avformat_open_input(&ic, _filename, NULL, &dict);
#else
    int err = av_open_input_file(&ic, _filename, NULL, 0, NULL);
#endif

    if (err < 0)
    {
        CV_WARN("Error opening file");
        CV_WARN(_filename);
        goto exit_func;
    }
    err =
#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(53, 6, 0)
    avformat_find_stream_info(ic, NULL);
#else
    av_find_stream_info(ic);
。。。。。。

可以看到在不同的在Cpp中又出现了#if,表示如果条件允许,则把范围内的代码编译进去,否则不进行编译。根据if else的代码我们可以看到ffmpeg代码的函数名称与参数的变化。还有一点我们终于明白ic的缩写含义了interrupt callback

ffmpeg代码分析

在上面的代码最重要的部分是读取文件,我们可以看到根据不同版本,分别是avformat_open_inputav_open_input_file
使用文件内容搜索工具搜索ffmpeg3.3.2,可以查找目录build/ffmpeg/ffmpeg-3.3.2/libavformat/utils.c内部具有avformat_open_input函数。头文件名称为avformat.h。如下由于篇幅仅仅展示avformat.h关于该函数的描述

/**
 * Open an input stream and read the header. The codecs are not opened.
 * The stream must be closed with avformat_close_input().
 *
 * @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).
 *           May be a pointer to NULL, in which case an AVFormatContext is allocated by this
 *           function and written into ps.
 *           Note that a user-supplied AVFormatContext will be freed on failure.
 * @param url URL of the stream to open.
 * @param fmt If non-NULL, this parameter forces a specific input format.
 *            Otherwise the format is autodetected.
 * @param options  A dictionary filled with AVFormatContext and demuxer-private options.
 *                 On return this parameter will be destroyed and replaced with a dict containing
 *                 options that were not found. May be NULL.
 *
 * @return 0 on success, a negative AVERROR on failure.
 *
 * @note If you want to use custom IO, preallocate the format context and set its pb field.
 */
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);

通过描述,我们可以知道opencv源代码中的ic的数据结构为AVFormatContext

© 著作权归作者所有

共有 人打赏支持
抢小孩糖吃

抢小孩糖吃

粉丝 67
博文 200
码字总数 223760
作品 0
东城
程序员
基于 JavaFX 开发的聊天客户端 - OIM

一、简介 OIM是一套即时通讯的聊天系统,在这里献给大家,一方面希望能够帮助对即时通讯有兴趣研究的朋友,希望我们能够共同进步,另一个就是希望能够帮助到需要即时通讯系统的朋友或者企业,...

烙灵 ⋅ 2017/06/09 ⋅ 23

javacv转流,jvm异常停止

下面是hserrpid.log的异常信息 # SIGSEGV (0xb) at pc=0x00007f399ca531ff, pid=4843, tid=0x00007f399dfa3700 JRE version: Java(TM) SE Runtime Environment (8.0161-b12) (build 1.8.0161......

菩提树下的猫 ⋅ 05/02 ⋅ 0

ffmpeg怎么能做到无缝切换呢

用ffmpeg怎么无缝切换视频源呢?我试过先把进程关闭然后重新推流,但是这样会导致中间卡顿20秒左右,也试过用javacv循环推流,但是帧数一直上不去,请问一下还有什么方法解决。

星卡农 ⋅ 2016/12/29 ⋅ 1

操作系统u盘启动扇区

org 07c00h ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行 BaseOfStack equ 07c00h ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长) BaseOfLoader equ 09000h ; L...

fxstar1 ⋅ 2012/07/12 ⋅ 0

从bootloader到用户程序执行的跳转

c08_mbr.asm c08.asm

firebroo ⋅ 2016/07/20 ⋅ 0

磁盘的LBA读取

INT 13h AH=41h: Check Extensions Present 参数: 寄存器: AH 41h = function number for extensions check DL drive index (e.g. 1st HDD = 80h) BX 55AAh 返回值: CF Set On Not Presen......

壶漏子 ⋅ 2016/12/15 ⋅ 0

java实现图片转换为视频

前一阵子,在网上碰到一个jar,可以把一组图片转换为视频,个人觉得挺强大的。于是就写了一个demo,结果还真可以,问题就是视频比较大,估计是编码问题。文章结尾会提供完整工程下载地址,供...

fengli3863 ⋅ 2012/03/23 ⋅ 0

Audio Unit和ExtendedAudioFile播放音频

前言 相关文章: 使用VideoToolbox硬编码H.264 使用VideoToolbox硬解码H.264 使用AudioToolbox编码AAC 使用AudioToolbox播放AAC HLS点播实现(H.264和AAC码流) HLS推流的实现(iOS和OS X系统...

落影loyinglin ⋅ 2017/10/25 ⋅ 0

视频转换格式后如何保保证清晰度

视频转换格式后如何保保证清晰度 这个话题主要是针对不能导入非编软件的某些视频格式。比如RM、RMVB、MKV…… 首先,你要知道需要转换的视频的媒体信息,这个可以用很多软件得到。比如KMPLA...

王一字 ⋅ 2015/08/19 ⋅ 0

ARM LDR 伪指令辨析

ARM LDR 伪指令的格式: LDR Rn, =expr 如果name是立即数的话 LDR R0,=0X123;//将0X123存入R0 如果name时个标识符 LDR R0,=NAME;//将NAME的地址存入R0 LDR R0, =0x3FF5000 ; 伪指令: 把 0x...

曾赛 ⋅ 2011/06/11 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

面试-JVM 内存结构

JVM 内存结构

秋日芒草 ⋅ 6分钟前 ⋅ 0

马氏距离与欧氏距离

马氏距离 马氏距离也可以定义为两个服从同一分布并且其协方差矩阵为Σ的随机变量之间的差异程度。 如果协方差矩阵为单位矩阵,那么马氏距离就简化为欧氏距离,如果协方差矩阵为对角阵,则其也...

漫步当下 ⋅ 29分钟前 ⋅ 0

聊聊spring cloud的RequestRateLimiterGatewayFilter

序 本文主要研究一下spring cloud的RequestRateLimiterGatewayFilter GatewayAutoConfiguration @Configuration@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMi......

go4it ⋅ 今天 ⋅ 0

Spring JavaConfig 注解

JavaConfig注解允许开发者将Bean的定义和配置放在Java类中。它是除使用XML文件定义和配置Bean外的另一种方案。 配置: 如一个Bean如果在XML文件可以这样配置: <bean id="helloBean" class="...

霍淇滨 ⋅ 今天 ⋅ 0

Spring clound 组件

Spring Cloud技术应用从场景上可以分为两大类:润物无声类和独挑大梁类。 润物无声,融合在每个微服务中、依赖其它组件并为其提供服务。 Ribbon,客户端负载均衡,特性有区域亲和、重试机制。...

英雄有梦没死就别停 ⋅ 今天 ⋅ 0

Confluence 6 重新获得站点备份文件

Confluence 将会创建备份,同时压缩 XML 文件后存储熬你的 <home-directory>/backups> 目录中。你需要自己访问你安装的 Confluence 服务器,并且从服务器上获得这个文件。 运行从 Confluence...

honeymose ⋅ 今天 ⋅ 0

informix的常用SQL语句

1、创建数据库 eg1. 创建不记录日志的库testdb,参考语句如下: CREATE DATABASE testdb; eg2. 创建带缓冲式的记录日志的数据库testdb(SQL语句不一定在事务之中,拥有者名字不被用于对象的解...

wangxuwei ⋅ 今天 ⋅ 0

matplotlib画图

最简单的入门是从类 MATLAB API 开始,它被设计成兼容 MATLAB 绘图函数。 from pylab import *from numpy import *x = linspace(0, 5, 10)y = x ** 2figure()plot(x, y, 'r')...

Dr_hu ⋅ 今天 ⋅ 0

RabbitMQ学习以及与Spring的集成(三)

本文介绍RabbitMQ与Spring的简单集成以及消息的发送和接收。 在RabbitMQ的Spring配置文件中,首先需要增加命名空间。 xmlns:rabbit="http://www.springframework.org/schema/rabbit" 其次是模...

onedotdot ⋅ 今天 ⋅ 0

JAVA实现仿微信红包分配规则

最近过年发红包拜年成为一种新的潮流,作为程序猿对算法的好奇远远要大于对红包的好奇,这里介绍一种自己想到的一种随机红包分配策略,还请大家多多指教。 算法介绍 一、红包金额限制 对于微...

小致dad ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部