FFMPEG Tips (2) 如何提取码流的基本信息

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

1.  码流中的哪些信息值得关注 ?


[ ] 是否包含:音频、视频

[ ] 码流的封装格式

[ ] 视频的编码格式

[ ] 音频的编码格式

[ ] 视频的分辨率、帧率、码率

[ ] 音频的采样率、位宽、通道数

[ ] 码流的总时长

[ ] 其他 Metadata 信息,如作者、日期等


2. 为什么需要拿到这些信息 ?


[ ] 码流的封装格式 -> 解封装

[ ] 音频、视频的编码格式 ->  初始化解码器

[ ] 视频的分辨率、帧率、码率 -> 视频的渲染

[ ] 音频的采样率、位宽、通道数 -> 初始化音频播放器

[ ] 码流的总时长 -> 展示、拖动

[ ] 其他 Metadata 信息 -> 展示


3. 这些关键信息都藏在哪 ?


这些关键的媒体信息,被称作 “metadata”,常常记录在整个码流的开头或者结尾处,例如:wav 格式主要由 wav header 头来记录音频的采样率、通道数、位宽等关键信息;mp4 格式,则存放在 moov box 结构中;而 FLV 格式则记录在 onMetaData 中等等。


我们可以看看 FLV 格式的 onMetaData 记录的信息包含有哪些内容:


wKioL1gdtA7DCJiJAAK6_kVE04M795.png


当然,并不是所有的码流都能简单地通过 "metadata" 解析出这些媒体信息,有些码流还需要通过试读、解码等一系列复杂的操作判断之后,才能准确地判断真实的媒体信息,在 ffmpeg 中,函数 avformat_find_stream_info 就是干这事的。


4. 如何从 ffmpeg 取出这些信息 ?


(1)首先打开码流,并解析“metadata”


播放器要完成的第一件事,就是 “打开码流”,然后再“ 解析码流信息”,在 ffmpeg 中,这两步任务主要通过 `avformat_open_input` 和 `avformat_find_stream_info`  函数来完成,前者负责服务器的连接和码流头部信息的拉取,后者则主要负责媒体信息的探测和分析工作,这两步的示例代码如下:


1
2
3
4
5
6
7
8
9
10
11
AVFormatContext *ic = avformat_alloc_context();
 
if  (avformat_open_input(&ic, url, NULL, NULL) < 0) {
     LOGE( "could not open source %s" , url);
     return  -1;
}
 
if  (avformat_find_stream_info(ic, NULL) < 0) {
     LOGE( "could not find stream information" );
     return  -1;
}


当这两步执行成功后,媒体信息就已经成功保存在了 ffmpeg 相关的结构体成员变量中了,下一步我们看看如何拿到这些信息,为我所用。


(2)利用 ffmpeg 系统函数 dump 码流信息


ffmpeg 提供了一个函数直接帮助你打印出解析到的媒体信息,用法如下:


1
av_dump_format(ic, 0, ic->filename, 0);


例如,打印 “rtmp://live.hkstv.hk.lxdns.com/live/hks” 的结果如下:


wKioL1gdtMOTJRRnAAJn1__abV4819.jpg


不过,这样打印的信息还不够,我们希望能通过代码取到每一个关键的媒体信息。因此,下面我们看看如何直接从 AVFormatContext 上下文结构体中提取这些信息。


(3)手动从 ffmpeg 的上下文结构体中提取


首先,我们看看 AVFormatContext 变量有哪些跟媒体信息有关的成员变量:


1
2
3
4
5
6
7
struct  AVInputFormat *iformat;  // 记录了封装格式信息
- unsigned  int  nb_streams;   // 记录了该 URL 中包含有几路流
- AVStream **streams;    // 一个结构体数组,每个对象记录了一路流的详细信息
- int64_t start_time;   // 第一帧的时间戳
- int64_t duration;     // 码流的总时长
int  bit_rate;             // 码流的总码率,bps
- AVDictionary *metadata;   // 一些文件信息头,key/value 字符串


由此可见,封装格式、总时长和总码率可以拿到了。另外,由于 AVStream **streams 还详细记录了每一路流的媒体信息,可以进一步挖一挖,看看它有哪些成员变量。


我们通过 av_find_best_stream 函数来取出指向特定指定路数的 AVStream 对象,比如视频流的 AVStream 和 音频流的 AVStream 对象分别通过如下方法来取到:


1
2
3
4
5
int  video_stream_idx = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVStream video_stream = ic->streams[video_stream_idx];
 
int  audio_stream_idx = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
AVStream audio_stream = ic->streams[audio_stream_idx];


拿到了 video_stream 和 audio_stream ,我们就可以把 AVStream 结构体中的信息提取出来了,其关键的成员变量如下:


1
2
3
4
5
6
- AVCodecContext *codec;    // 记录了该码流的编码信息
- int64_t start_time;    // 第一帧的时间戳
- int64_t duration;       // 该码流的时长
- int64_t nb_frames;   // 该码流的总帧数
- AVDictionary *metadata;   // 一些文件信息头,key/value 字符串
- AVRational avg_frame_rate;   // 平均帧率


到这里,我们拿到了平均的帧率,其中,AVCodecContext 详细记录了每一路流的具体的编码信息,我们再进一步挖一挖,看看 AVCodecContext 有哪些成员变量。


1
2
3
4
5
6
7
8
9
10
11
12
13
const  struct  AVCodec  *codec;  // 编码的详细信息
enum  AVCodecID  codec_id;      // 编码类型
int  bit_rate;   // 平均码率
 
/* video only */
int  width, height;            // 图像的宽高尺寸,码流中不一定存在该信息,会由解码后覆盖
enum  AVPixelFormat pix_fmt;   // 原始图像的格式,码流中不一定存在该信息,会由解码后覆盖
 
/* audio only */
int  sample_rate;        // 音频的采样率
int  channels;           // 音频的通道数
enum  AVSampleFormat sample_fmt;    // 音频的格式,位宽
int  frame_size;         // 每个音频帧的 sample 个数


原来我们最关心的编码类型、图片的宽高、音频的参数藏在这里了!经过层层解析后,我们想要的媒体信息,基本上在这些结构体变量中都找到了。


5.  代码示例


我们可以尝试手动把我们找到的媒体信息都打印出来看看,代码示例如下(你也可以到我的 Github 查看源代码: https://github.com/Jhuster/clib):


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <libavutil/log.h>
 
#define LOGD(format, ...) av_log(NULL, AV_LOG_DEBUG, format, ##__VA_ARGS__);
 
int  ff_dump_stream_info( char  * url)
{
     AVFormatContext *ic = avformat_alloc_context();
 
     if  (avformat_open_input(&ic, url, NULL, NULL) < 0) {
         LOGD( "could not open source %s" , url);
         return  -1;
     }
 
     if  (avformat_find_stream_info(ic, NULL) < 0) {
         LOGD( "could not find stream information" );
         return  -1;
     }
 
     LOGD( "---------- dumping stream info ----------" );
 
     LOGD( "input format: %s" , ic->iformat->name);
     LOGD( "nb_streams: %d" , ic->nb_streams);
 
     int64_t start_time = ic->start_time / AV_TIME_BASE;
     LOGD( "start_time: %lld" , start_time);
 
     int64_t duration = ic->duration / AV_TIME_BASE;
     LOGD( "duration: %lld s" , duration);
 
     int  video_stream_idx = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
     if  (video_stream_idx >= 0) {
         AVStream *video_stream = ic->streams[video_stream_idx];
         LOGD( "video nb_frames: %lld" , video_stream->nb_frames);
         LOGD( "video codec_id: %d" , video_stream->codec->codec_id);
         LOGD( "video codec_name: %s" , avcodec_get_name(video_stream->codec->codec_id));
         LOGD( "video width x height: %d x %d" , video_stream->codec->width, video_stream->codec->height);
         LOGD( "video pix_fmt: %d" , video_stream->codec->pix_fmt);
         LOGD( "video bitrate %lld kb/s" , (int64_t) video_stream->codec->bit_rate / 1000);
         LOGD( "video avg_frame_rate: %d fps" , video_stream->avg_frame_rate.num/video_stream->avg_frame_rate.den);
     }
 
     int  audio_stream_idx = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
     if  (audio_stream_idx >= 0) {
         AVStream *audio_stream = ic->streams[audio_stream_idx];
         LOGD( "audio codec_id: %d" , audio_stream->codec->codec_id);
         LOGD( "audio codec_name: %s" , avcodec_get_name(audio_stream->codec->codec_id));
         LOGD( "audio sample_rate: %d" , audio_stream->codec->sample_rate);
         LOGD( "audio channels: %d" , audio_stream->codec->channels);
         LOGD( "audio sample_fmt: %d" , audio_stream->codec->sample_fmt);
         LOGD( "audio frame_size: %d" , audio_stream->codec->frame_size);
         LOGD( "audio nb_frames: %lld" , audio_stream->nb_frames);
         LOGD( "audio bitrate %lld kb/s" , (int64_t) audio_stream->codec->bit_rate / 1000);
     }
 
     LOGD( "---------- dumping stream info ----------" );
 
     avformat_close_input(&ic);
}


本文转自 Jhuster 51CTO博客,原文链接:http://blog.51cto.com/ticktick/1869849,如需转载请自行联系原作者

相关文章
|
1月前
|
缓存 监控 计算机视觉
视频监控笔记(三):opencv结合ffmpeg获取rtsp摄像头相关信息
本文介绍了如何使用OpenCV结合FFmpeg获取RTSP摄像头信息,包括网络架构、视频监控系统组成、以及如何读取和显示网络摄像头视频流。
41 1
|
5月前
|
存储 编解码
FFmpeg开发笔记(三十)解析H.264码流中的SPS帧和PPS帧
《FFmpeg开发实战》书中介绍了音视频编码历史,重点讲述H.264的成功在于其分为视频编码层和网络抽象层。H.264帧类型包括SPS(序列参数集,含视频规格参数),PPS(图像参数集,含编码参数)和IDR帧(立即解码刷新,关键帧)。SPS用于计算视频宽高和帧率,PPS存储编码设置,IDR帧则标志新的解码序列。书中还配以图片展示各帧结构详情,完整内容可参考相关书籍。
216 7
FFmpeg开发笔记(三十)解析H.264码流中的SPS帧和PPS帧
|
6月前
|
编解码 API 开发工具
FFmpeg获取音视频流信息
FFmpeg获取音视频流信息
137 1
FFmpeg获取音视频流信息
|
6月前
|
存储 编解码 索引
FFmpeg代码编程获取视频信息
FFmpeg代码编程获取视频信息
377 0
|
6月前
|
C语言 C++
ffmpeg解码之使用C语言打印音视频信息
ffmpeg解码之使用C语言打印音视频信息
104 0
|
6月前
|
Linux C语言
ffmpeg编程查看视频文件信息
ffmpeg编程查看视频文件信息
112 0
|
存储 编解码
ffmpeg解码提取帧RGB格式信息
使用ffmpeg和qt实现播放视频功能
309 0
|
存储 编解码
ffmpeg CBR精准码流控制三个步骤
ffmpeg CBR精准码流控制三个步骤
467 0
【C#】【ffmpeg】外部调用线程执行ffmepg读取返回的信息乱码问题
【C#】【ffmpeg】外部调用线程执行ffmepg读取返回的信息乱码问题
195 0
【C#】【ffmpeg】外部调用线程执行ffmepg读取返回的信息乱码问题

热门文章

最新文章