FFmpeg之FFprobe检测多媒体格式

FFmpeg之FFprobe检测多媒体格式FFmpeg 里面有一个模块 FFprobe https ffmpeg org ffprobe html 专门用来检测多媒体格式数据 它的作用类似 Android 中的 MediaMetadat FFprobe 支持检测 format streams frames 用法与 FFmpeg 类似 我们可以单独使用 也可以结合在一起使用 下面举例说明一下 1 format 仅是显示 forma

FFmpeg里面有一个模块FFprobe(https://ffmpeg.org/ffprobe.html)专门用来检测多媒体格式数据,它的作用类似Android中的MediaMetadataRetriever。FFprobe支持检测format、streams、frames,用法与FFmpeg类似,我们可以单独使用,也可以结合在一起使用,下面举例说明一下:

1、format

仅是显示format:ffprobe -i xxx.mp4 -show_format

既显示又打印format:ffprobe -i xxx.mp4 -show_format -print_format json

2、streams

显示音视频流:ffprobe -i xxx.mp4 -show_streams

3、frames

显示视频帧:ffprobe -i xxx.mp4 -show_frames

4、format与streams

ffprobe -i xxx.mp4 -show_streams -show_format -print_format json

最终打印出来是json格式(也可以选择xml格式),我们需要解析一下json数据,提取我们需要的信息,最终数据如下图:

FFmpeg之FFprobe检测多媒体格式

看到上图,伙伴们有木有恍然大悟,感觉到与Android的MediaMetadataRetriever的功能似曾相识呢?FFprobe可以检测的多媒体信息包括:时长、码率、文件大小、封装格式、多媒体流的个数、视频分辨率、宽高比、像素格式、帧率、视频编码器、音频采样率、声道个数、声道布局、音频编码器等等。它比Android自带的MediaMetadataRetriever的优势在于:ffprobe可支持更多多媒体格式,兼容性好;而MediaMetadataRetriever取决于系统的硬解码器,不同手机的MediaCodec编解码能力存在差异。下面我们来具体讨论下FFprobe检测多媒体信息的实现过程:

一、修改ffprobe源码

在ffprobe.c源码中,是没有对外方法提供jni调用的,也没有返回字符串结果。所以我们需要修改下ffprobe.c源码:首先把main方法改为ffprobe_run方法,并且在ffprobe.h里面声明该方法,然后增加字符串打印方法。

//ffprobe主函数入口 char* ffprobe_run(int argc, char argv) { const Writer *w; WriterContext *wctx; char *buf; char *w_name = NULL, *w_args = NULL; int ret, i; //动态申请内存 buffer_length = 0; if(print_buffer == NULL) { print_buffer = av_malloc(sizeof(char) * buffer_size); } memset(print_buffer, '\0', (size_t) buffer_size); ... return print_buffer; }

编写打印json字符串方法:

void frank_printf_json(char *fmt, ...) { va_list args; va_start(args, fmt); int length = printf_json(print_buffer + buffer_length, buffer_size - buffer_length, fmt, args); buffer_length += length; va_end(args); }

在解析多媒体格式时,调用打印json字符串方法,把数据写入内存里:

static void json_print_section_header(WriterContext *wctx) { JSONContext *json = wctx->priv; AVBPrint buf; const struct section *section = wctx->section[wctx->level]; const struct section *parent_section = wctx->level ? wctx->section[wctx->level-1] : NULL; if (wctx->level && wctx->nb_item[wctx->level-1]) frank_printf_json(",\n"); if (section->flags & SECTION_FLAG_IS_WRAPPER) { frank_printf_json("{\n"); json->indent_level++; } else { av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); json_escape_str(&buf, section->name, wctx); JSON_INDENT(); json->indent_level++; if (section->flags & SECTION_FLAG_IS_ARRAY) { frank_printf_json("\"%s\": [\n", buf.str); } else if (parent_section && !(parent_section->flags & SECTION_FLAG_IS_ARRAY)) { frank_printf_json("\"%s\": {%s", buf.str, json->item_start_end); } else { frank_printf_json("{%s", json->item_start_end); /* this is required so the parser can distinguish between packets and frames */ if (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES) { if (!json->compact) JSON_INDENT(); frank_printf_json("\"type\": \"%s\"%s", section->name, json->item_sep); } } av_bprint_finalize(&buf, NULL); } } static void json_print_section_footer(WriterContext *wctx) { JSONContext *json = wctx->priv; const struct section *section = wctx->section[wctx->level]; if (wctx->level == 0) { json->indent_level--; frank_printf_json("\n}\n"); } else if (section->flags & SECTION_FLAG_IS_ARRAY) { frank_printf_json("\n"); json->indent_level--; JSON_INDENT(); frank_printf_json("]"); } else { frank_printf_json("%s", json->item_start_end); json->indent_level--; if (!json->compact) JSON_INDENT(); frank_printf_json("}"); } } static inline void json_print_item_str(WriterContext *wctx, const char *key, const char *value) { AVBPrint buf; av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); frank_printf_json("\"%s\":", json_escape_str(&buf, key, wctx)); av_bprint_clear(&buf); frank_printf_json(" \"%s\"", json_escape_str(&buf, value, wctx)); av_bprint_finalize(&buf, NULL); } static void json_print_str(WriterContext *wctx, const char *key, const char *value) { JSONContext *json = wctx->priv; if (wctx->nb_item[wctx->level]) frank_printf_json("%s", json->item_sep); if (!json->compact) JSON_INDENT(); json_print_item_str(wctx, key, value); } static void json_print_int(WriterContext *wctx, const char *key, long long int value) { JSONContext *json = wctx->priv; AVBPrint buf; if (wctx->nb_item[wctx->level]) frank_printf_json("%s", json->item_sep); if (!json->compact) JSON_INDENT(); av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); frank_printf_json("\"%s\": %lld", json_escape_str(&buf, key, wctx), value); av_bprint_finalize(&buf, NULL); }

二、封装jni方法

提供jni方法,供java层调用ffprobe_run(),实现多媒体格式的解析:

FFMPEG_FUNC(jstring , handleProbe, jobjectArray commands) { int argc = (*env)->GetArrayLength(env, commands); char argv = (char)malloc(argc * sizeof(char*)); int i; for (i = 0; i < argc; i++) { jstring jstr = (jstring) (*env)->GetObjectArrayElement(env, commands, i); char* temp = (char*) (*env)->GetStringUTFChars(env, jstr, 0); argv[i] = malloc(1024); strcpy(argv[i], temp); (*env)->ReleaseStringUTFChars(env, jstr, temp); } //execute ffprobe command char* result = ffprobe_run(argc, argv); //release memory for (i = 0; i < argc; i++) { free(argv[i]); } free(argv); return (*env)->NewStringUTF(env, result); }

三、java层调用jni方法

在java层使用native关键字声明jni方法,开启子线程调用:

/ * execute probe cmd internal * @param commands commands * @param onHandleListener onHandleListener */ public static void executeProbe(final String[] commands, final OnHandleListener onHandleListener) { new Thread(new Runnable() { @Override public void run() { if(onHandleListener != null) { onHandleListener.onBegin(); } //execute ffprobe String result = handleProbe(commands); int resultCode = !TextUtils.isEmpty(result) ? RESULT_SUCCESS : RESULT_ERROR; if(onHandleListener != null) { onHandleListener.onEnd(resultCode, result); } } }).start(); } private native static String handleProbe(String[] commands);

四、监听FFprobe运行状态

传入字符串参数,调用executeFFprobeCmd方法,实现onHandlerListener监听:

/ * execute probe cmd * @param commandLine commandLine */ public void executeFFprobeCmd(final String[] commandLine) { if(commandLine == null) { return; } FFmpegCmd.executeProbe(commandLine, new OnHandleListener() { @Override public void onBegin() { mHandler.obtainMessage(MSG_BEGIN).sendToTarget(); } @Override public void onEnd(int resultCode, String resultMsg) { MediaBean mediaBean = null; if(resultMsg != null && !resultMsg.isEmpty()) { mediaBean = JsonParseTool.parseMediaFormat(resultMsg); } mHandler.obtainMessage(MSG_FINISH, mediaBean).sendToTarget(); } }); }

五、封装FFprobe命令

在文章的开头,我们已经介绍过ffprobe命令,更多更详细的命令请参考文档:https://ffmpeg.org/ffprobe.html.ffprobe命令结构分为三部分:ffprobe+(-i filePath)输入文件路径+(-show_streams -show_frames -show_format)执行主体。

public static String[] probeFormat(String inputFile) { String ffprobeCmd = "ffprobe -i %s -show_streams -show_format -print_format json"; ffprobeCmd = String.format(Locale.getDefault(), ffprobeCmd, inputFile); return ffprobeCmd.split(" "); }

六、解析json数据

调用FFprobe函数,返回json字符串结果后,我们需要进一步解析,提取我们需要的信息:

public static MediaBean parseMediaFormat(String mediaFormat) { if (mediaFormat == null || mediaFormat.isEmpty()) { return null; } MediaBean mediaBean = null; try { JSONObject jsonMedia = new JSONObject(mediaFormat); JSONObject jsonMediaFormat = jsonMedia.getJSONObject("format"); mediaBean = new MediaBean(); int streamNum = jsonMediaFormat.optInt("nb_streams"); mediaBean.setStreamNum(streamNum); String formatName = jsonMediaFormat.optString("format_name"); mediaBean.setFormatName(formatName); String bitRateStr = jsonMediaFormat.optString("bit_rate"); if (!TextUtils.isEmpty(bitRateStr)) { mediaBean.setBitRate(Integer.valueOf(bitRateStr)); } String sizeStr = jsonMediaFormat.optString("size"); if (!TextUtils.isEmpty(sizeStr)) { mediaBean.setSize(Long.valueOf(sizeStr)); } String durationStr = jsonMediaFormat.optString("duration"); if (!TextUtils.isEmpty(durationStr)) { float duration = Float.valueOf(durationStr); mediaBean.setDuration((long) duration); } JSONArray jsonMediaStream = jsonMedia.getJSONArray("streams"); if (jsonMediaStream == null) { return mediaBean; } for (int index = 0; index < jsonMediaStream.length(); index ++) { JSONObject jsonMediaStreamItem = jsonMediaStream.optJSONObject(index); if (jsonMediaStreamItem == null) continue; String codecType = jsonMediaStreamItem.optString("codec_type"); if (codecType == null) continue; if (codecType.equals(TYPE_VIDEO)) { VideoBean videoBean = new VideoBean(); mediaBean.setVideoBean(videoBean); String codecName = jsonMediaStreamItem.optString("codec_tag_string"); videoBean.setVideoCodec(codecName); int width = jsonMediaStreamItem.optInt("width"); videoBean.setWidth(width); int height = jsonMediaStreamItem.optInt("height"); videoBean.setHeight(height); String aspectRatio = jsonMediaStreamItem.optString("display_aspect_ratio"); videoBean.setDisplayAspectRatio(aspectRatio); String pixelFormat = jsonMediaStreamItem.optString("pix_fmt"); videoBean.setPixelFormat(pixelFormat); String profile = jsonMediaStreamItem.optString("profile"); videoBean.setProfile(profile); int level = jsonMediaStreamItem.optInt("level"); videoBean.setLevel(level); String frameRateStr = jsonMediaStreamItem.optString("r_frame_rate"); if (!TextUtils.isEmpty(frameRateStr)) { String[] frameRateArray = frameRateStr.split("/"); double frameRate = Math.ceil(Double.valueOf(frameRateArray[0]) / Double.valueOf(frameRateArray[1])); videoBean.setFrameRate((int) frameRate); } } else if (codecType.equals(TYPE_AUDIO)) { AudioBean audioBean = new AudioBean(); mediaBean.setAudioBean(audioBean); String codecName = jsonMediaStreamItem.optString("codec_tag_string"); audioBean.setAudioCodec(codecName); String sampleRateStr = jsonMediaStreamItem.optString("sample_rate"); if (!TextUtils.isEmpty(sampleRateStr)) { audioBean.setSampleRate(Integer.valueOf(sampleRateStr)); } int channels = jsonMediaStreamItem.optInt("channels"); audioBean.setChannels(channels); String channelLayout = jsonMediaStreamItem.optString("channel_layout"); audioBean.setChannelLayout(channelLayout); } } } catch (Exception e) { Log.e(TAG, "parse error=" + e.toString()); } return mediaBean; }

折腾这么久,终于解析到多媒体格式相关数据了,详细源码可以到Github查看:https://github.com/xufuji456/FFmpegAndroid

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/203783.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午9:35
下一篇 2026年3月19日 下午9:36


相关推荐

  • bug生命周期以及管理

    bug生命周期以及管理目录 bug 管理工具 bug 的生命周期 bug 的优先级 Priority bug 管理工具禅道 jira bugzilla bugfree 等等 bug 的生命周期是指在 Bug 管理工具中 一个 bug 被发现到这个 bug 被关闭的过程 Bug 的生命周期被分成的阶段是 测试员 新建 测试员 指派 开发 接受 开发 修复 测试验证 关闭 bug 的优先级 Priority bug 的优先级指 bug 必须被修复的紧急程度 1 立即解决缺陷导致系统几乎不能使用或者测试不能继续 需立即修复 2 高

    2026年3月26日
    1
  • volatile关键字作用

    volatile关键字作用一、作用简述内存可见性:保证变量的可见性:当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果。当一个线程向被volatile关键字修饰的变量写入数据的时候,虚拟机会强制它被值刷新到主内存中。当一个线程用到被volatile关键字修饰的值的时候,虚拟机会强制要求它从主内存中读取。 屏蔽JVM指令重排序(防止JVM编译源码生成class时使用重排序)…

    2022年6月1日
    38
  • 每天一道算法_4_Hangover

    此系列刚开始两天就被十一假期打断了,去山西玩了几天,今天刚回来,为了弥补一下心里的貌似隐隐作痛的愧疚感,补上一刀。今天的题目是 Hangover,如下: DescriptionHow far can you make a stack of cards overhang a table? If you have one card, you can create a max

    2022年3月10日
    36
  • 一级域名301重定向到www二级域名

    一级域名301重定向到www二级域名301重定向有利于百度的搜索例如一个域名www.test.com如果不做设置会产生4个网址,(1)test.com(2)www.test.com(3)test.com/default.html(4)www.test.com/default.html会导致网址的PR值被分散.htaccess设置Options+FollowSymLinksIndexIgnore*/*…

    2022年5月21日
    60
  • mybatis返回值_存储过程获取查询结果

    mybatis返回值_存储过程获取查询结果com.jerry.mapper.TestMapper.javapackagecom.jerry.mapper;importjava.util.List;importjava.util.Map;publicinterfaceTestMapper{ /** *查寻单个结果直接返回Map&amp;amp;amp;lt;String,Object&amp;amp;amp;gt; *@paramid *…………..

    2022年10月4日
    5
  • ceph存储修改vm密钥(密码)

    ceph存储修改vm密钥(密码)有台名为 nginx 的 vm 出现了些怪异的行为 想把根磁盘导出来挂载在本地看看到底是怎么回事 如果 nova 使用的是本地存储 vmdisk 会存在相应 compute 节点的 var lib nova instance xxxx disk 中 直接使用 mount 命令井进行本地挂载即可 或者使用 libguestfs tools 工具套件 但是如果使用的是 rbd 存储 vm 的 disk 会存在 cephpool 里

    2026年3月17日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号