前言
这篇博文记录一个简单的实时码流测试程序,事实上FFmpeg打开媒体文件后就可以获得整个视频的平均码流**(只计算视频码流**),但是无法获取实时码流,因为后面的工作需要对编解码做一些优化,需要实时观测码流,这里先实现一个比较简单的版本。
运行结果
事实上h264编码的视频的码流还是比较平缓的,在视频较暗或者视角相对固定的时候可以观测到码流稍有下降。这里单位用Mbps。
可以观测到实时码率,大家也可以用这款软件Elecard StreamEye来观测实时码流,如下图:蓝色的曲线就是码流的实时曲线。
码流
码流(Data Rate)是指视频文件在单位时间内使用的数据流量,也叫码率或码流率,是视频编码中画面质量控制中最重要的部分,一般我们用的单位是kb/s或者Mb/s。一般来说同样分辨率下,视频文件的码流越大,压缩比就越小,画面质量就越高。码流越大。我们实验室想要达到的目的就是在人眼觉察不到视频质量下降的前提下,降低码流,以此达到节省带宽的目的,这样在实时视频的应用背景下可以实现视频的更高分辨率和更高帧率,在非实时的应用场景下也可以实现节省存储空间的目的。
码流的计算十分简单,这里就不做详细介绍。
实现方法
其实就是在解封装后用av_read_frame()去读取帧,用列表把帧存储起来,列表的长度=视频的帧率。这样就可以对实时码率就行计算了。
源代码:
#include<stdio.h>
#include<List>
#include<windows.h>
extern "C"
{
#include<libavformat/avformat.h>
}
int main(int argc, char* argv[])
{
//本地媒体文件或者网络流地址
char url[] = "test.mov";
//定义解封装格式上下文。
AVFormatContext* ifmt = NULL;
//读取的压缩后媒体信息。
AVPacket* pkt = 0;
//媒体文件的帧率
int frame_rate = 0;
//视频流索引
int video_index = -1;
//用于存放pkt的链表
std::list<AVPacket>*pkt_list=new std::list<AVPacket>();
std::list<AVPacket>::iterator iter;
//打开媒体文件
int re = avformat_open_input(&ifmt, url, 0, 0);
//错误信息buff
char buff[1024] = { 0 };
if (re < 0)
{
av_strerror(re, buff, sizeof(buff - 1));
printf("Open the file failed :%s\n", buff);
return -1;
}
printf("Open the file: %s Successed!\n", url);
//探测信息。
re=avformat_find_stream_info(ifmt, 0);
if (re < 0)
{
printf("avformat_find_stream_info failed!\n");
return -1;
}
//输出媒体信息
av_dump_format(ifmt,-1,url,0);
video_index=av_find_best_stream(ifmt, AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0);
if (video_index < 0)
{
printf("未检测到视频流信息!\n");
return -1;
}
//计算帧率,+0.5是为了四舍五入
frame_rate = (double)ifmt->streams[video_index]->avg_frame_rate.num / (double)ifmt->streams[video_index]->avg_frame_rate.den+0.5;
/*AVPacket使用前需要使用av_packet_alloc()申请空间,
* 结束后需要使用av_packet_free()释放空间。
*/
pkt = av_packet_alloc();
int pts=0;
printf("=================================================================\n");
printf("**************************实时码率检测***************************\n");
printf("=================================================================\n");
while (1)
{
int re=av_read_frame(ifmt, pkt);
//如果读取失败退出
if (re < 0)
{
printf("文件读取结束\n");
break;
}
//读取的不是视频文件则释放pkt的内存继续读取
if (pkt->stream_index != video_index)
{
av_packet_unref(pkt);
continue;
}
pkt_list->push_back(*pkt);
//计算pts,换算
pts = pkt->pts *1000* av_q2d(ifmt->streams[video_index]->time_base);
//还不够一秒暂时不进行计算
if (pkt_list->size() < frame_rate)
{
continue;
}
//帧数正好一秒进行计算。
if (pkt_list->size() == frame_rate)
{
double instant_biterate = 0;
AVPacket* packet =0;
for (iter = pkt_list->begin(); iter != pkt_list->end(); iter++)
{
packet = &*iter;
instant_biterate = packet->size + instant_biterate;
}
//*8是bps,不乘8是Byte/s
printf("显示时间戳(pts): %d ms ,实时码率(dinstant bitrate): %lf Mbps\n",pts,instant_biterate*8/1000000);
//列表满了以后就删掉一个数据,不要忘记为删掉的数据清理空间。
packet = &pkt_list->front();
av_packet_unref(packet);
pkt_list->pop_front();
}
Sleep(40); //延时,为了看清效果,可以删掉。
}
/*
* 读取文件结束开始清理空间,实际上在这个程序里运行完就直接结束了,
* 为了养成良好的习惯我们还是把空间都清理掉。
*/
//av_packet_free()调用后就不用在列表里挨个的释放pkt的内存了。
av_packet_free(&pkt);
pkt_list->clear();//列表清空
delete pkt_list;
avformat_free_context(ifmt);
return 0;
}