1.音视频采样编码的基本原理
将模拟信号转化为数字信号,隔段时间进行采样
2.ffmpeg的基本情况
介绍
Fmpeg 是领先的多媒体框架,能够解码、编码、转码、混合、解密、流媒体、过滤和播放人类和机器创造的几乎所有东西
。它支持最晦涩的古老格式,直到最尖端的格式。无论它们是由某个标准委员会、社区还是公司设计的。它还具有高度的便携性。
FFmpeg 可以在 多个平台构建环境、机器架构和配置下编译、运行,并通过测试基础设施 FATE。
FFmpeg命令行工具获取
- 下载地址
- 访问FFmpeg官网(http://ffmpeg.org)选择Download,选择Windows Package进入Zeranoe FFmpeg网站。
- 注意不要直接从FFmpeg官网下载源代码。
- 版本说明
- Zeranoe网站中的FFmpeg分为3个版本:
- static:只包含3个体积很大的exe文件。
- shared:除了3个体积较小的exe文件外,还包含了dll动态库文件
- Dev:只包含了开发用的头文件(*.h)和导入库文件(*.lib)
- Zeranoe网站中的FFmpeg分为3个版本:
- 一般使用命令行时下载Static或者Shared版本就可以了
- 进行开发需要下载Dev版本
ffmpeg.exe的使用
-
命令格式
-
功能
ffmpeg.exe用于视频的转码 -
最简单的命令
ffmpeg -i input.avi -b:v 640k output.ts
-
该命令将当前文件夹下的input.avi转换为output.ts文件,并将其文件视频的码率设置为640kbps。
-
命令格式
ffmpeg -i (输入文件路径) -b:v (输出视频码率) (输出文件路径)
-
命令参数
参数 说明 -h 帮助 -i filename 输入文件 -t duration 设置处理时间,格式为hh:mm:ss -ss duration 设置起始时间,格式为hh:mm:ss -b:v bltrate 设置视频码率 -b:a bitrate 设置音频码率 -r fps 设置帧率 -s wxh 设置分辨率,格式为WxH(用*也可以) -c:v codec 设置视频编码器 -c:a codec 设置音频编码器 -ar freq 设置音频采样率 如: ffmpeg -i video.mkv video.mp4 将video从MKV格式转码为MP4格式并另外保存
-
ffplay.exe的使用
-
命令格式
-
功能
用于视频的播放 -
最简单的命令
ffplay input.avi
-
该命令将播放当前文件夹下的input.avi文件。
-
命令格式
ffmpeg (输入文件路径)
-
命令参数
快捷键 说明 q,ESC 退出 p,空格 暂停 鼠标右键屏幕 跳转到指定位置 -
解封装
-
av+register_all():注册所有组件
-
avformat_network_init()
-
avformat_open_input(…):打开输入视频文件
- 确保av_register_all、avformat_network_init已经停用
- AVFormatContext **ps
- const char *url
- AVInputFormat *fmt
- AVDIctionary **options
-
avformat_find_stream_info(…):获取视频文件信息
-
avcodec_find_decoder():查找解码器
-
avcodec_open2():打开解码器
-
av_find_best_stream(…)
-
AVFormatContext (封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息)
- AVIOContext *pd;char filename[1024];
- iformat //输入视频的AVInputFormat
- unsigned int nb_streams; //输入视频的AVStream
- AVStream **streams; //输入视频的AVStream []数组
- int64_t duration; //AV_TIME_BASE 输入视频的时长(以微秒为单位)
- int64_t bit_rate; //输入视频码率
- void avformat_close_input(AVFormatContent **s); //关闭输入视频文件,释放空间
-
AVInputFormat (每种封装格式(例如FLV,MKV,MP4,AVI)对应一个结构体)
- name:封装格式名称
- long_name:封装格式的长名称
- extensions:封装格式的扩展名
- id:封装格式ID
- 一些封装格式处理的接口函数
-
AVStream (视频文件中每个视频(音频)流对应一个结构体)
- AVCodecContext *codec; //过时了
- id //序号
- AVRational time_base; //分数表示,减少精度损失
- int64_t duration; //毫秒 duration (time_base,num/(double)time_base.den) 1000毫秒
- int64_t nb_frames;
- AVRational avg_frame_rate;
- AVCodecParameters *codecpar:(音视频参数)用于替代过时的1
- AVBufferRef *buff; //删除的时候-1
- int64_t pts; //pts * (num/den) 显示时间戳
- int64_t dts; //解码时间戳
- unit8_t *data; //压缩编码数据
- int size; //压缩编码数据大小
- stream_index //所属的AVStream
- AVPacket *av_packet_alloc(void); //创建并初始化,申请了堆上的空间,需要自己释放
- AVPacket *av_packet_clone(const AVPacket *src); //创建和并用计数
- int av_packet_ref(AVPacket *dst,const AVPacket *src);
- av_packet_unred(AVPacket *pkt);
- void av_packet_free(AVPacket **pkt); //清空对象并减引用计数
- void av_init_packet(AVPacket *pkt); //默认值
- int av_packet_from_data(AVPacket *pkt,unit7_t *data,int size);
- int av_copy_packet(AVPacket *dst,const AVPacket *src); ///attribute_deprecated用clone函数代替了
-
av_read_frame(…):从输入文件读取一帧压缩数据
- AVFormatContext *s;
- AVPacket *pkt; //不能传null
- return 0 if OK, < 0 on error or end of file
-
avcodec_decode_video2():解码一帧压缩数据
-
avcodec_close():关闭解码器
找寻
3.PCM过程的一些基本参数
4.视频的封装格式及播放的基本流程
视频播放器原理
- 封装格式(MP4,RMVB,TS,FLV,AVI)
- 视频编码数据(H.264.MPEG2,VC-1)
- 音频编码数据(AAC,MP3,AC-3)
- 视频像素数据(YUV420P,RGB)
- 音频采样数据(PCM)
播放视频流程
可以使用软件mediainfo进行读取。
5. ffmpeg的解码大致流程
FFmpeg解码的流程图如下所示
6.ffmpeg程序的环境配置
配置ffmpeg所依赖的文件(此处是VS的配置,Qt Creator单独配置):
- C++ -> 常规 -> 附加包含目录(ffmpeg的include目录)
- 链接器 -> 附加库目录(debug下面的x64)
- 调试->工作目录(代码中包含的文件(资料文件,视频文件)所需要的路径)
- 针对C语言需要多添加一句:
extern "C"{
#include "libacformat/avformat.h"
}
- 代码包含或者配置"附加依赖项"(包含lib库)
7.ffmpeg编写解码程序
第一部分
#include <QCoreApplication>
#include <QDebug>
extern "C" {
#include "libacformat/avformat.h"
}
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
static double FractionToDouble(AVRational fraction)//AVRational为分数表示,其目的为避免精度损失,分数表示为分子除以分母
{
return fraction.den == 0?0:(double)fraction.num/(double)fraction.den;
}
int main(int argc,char *argv[])
{
QCoreApplication a(argc,argv);
const char* path = "test.mp4";
//初始化封装库
av_register_all();
//打开输入视频文件
AVFormatContext *avfc = NULL;
int ret = avformat_open_input(&avfc,path,NULL,NULL); //传二级指针一般是对指针进行赋值的时候
if(ret != 0)
{
qDebug()<<"OPen"<<path<<"failed!";
exit(-1);
}
//获取流信息
ret = avformat_find_stream_info(avfc,NULL);
if(ret < 0)
{
qDebug()<<"avformat_find_stream_info_failed!";
exit(-1);
}
//总时长-秒数
qDebug()<<(avfc->duration / AV_TIME_BASE);
//avfc->streams
qDebug()<<"streams number is:"<<avfc->nb_streams;
for(int i = 0; i < avfc->nb->streams;i++)
{
qDebug()<<"i is "<<i;
AVStream* as = avfc->streams[i];
qDebug()<<as->codecpar->codec_id; //AV_CODEC_ID_H264(解码)
qDebug()<<as->codecpar->format;
qDebug();
//音频 AVMEDIA_TYPE_AUDIO
if(as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
qDebug()<<i<<"audio info";
qDebug()<<"sample_rate="<<as->codecpar->sample_rate;
qDebug()<<"Channels="<<as->codecpar->channels;
}
//视频 AVMEDIA_TYPE_VIDEO
else if(as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
qDebug()<<i<<"video info";
qDebug()<<"width="<<as->codecpar->width;
qDebug()<<"height="<<as->codecpar->height;
//帧率fps
qDebug()<<"video fps="<<FractionToDouble(as->avg_frame_rate);
}
}
return a.exec();
}
第二部分
#include <QCoreApplication>
#include <QDebug>
extern "C" {
#include "libacformat/avformat.h"
#include "libavcodec/avcodec.h" //解码
}
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
static double FractionToDouble(AVRational fraction)
{
return fraction.den == 0?0:(double)fraction.num/(double)fraction.den;
}
int main(int argc,char *argv[])
{
QCoreApplication a(argc,argv);
const char* path = "test.mp4";
av_register_all();
//初始化解码器
avcodec_register_all();
//音视频索引,读取时可以区别音视频
int videoStream = 0; //
int audioStream = 1; //
AVFormatContext *avfc = NULL;
int ret = avformat_open_input(&avfc,path,NULL,NULL);
if(ret != 0)
{
qDebug()<<"OPen"<<path<<"failed!";
exit(-1);
}
ret = avformat_find_stream_info(avfc,NULL);
if(ret < 0)
{
qDebug()<<"avformat_find_stream_info_failed!";
exit(-1);
}
qDebug()<<(avfc->duration / AV_TIME_BASE);
//avfc->streams
qDebug()<<"streams number is:"<<avfc->nb_streams;
for(int i = 0; i < avfc->nb->streams;i++)
{
qDebug()<<"i is "<<i;
AVStream* as = avfc->streams[i];
qDebug()<<as->codecpar->codec_id;
qDebug()<<as->codecpar->format;
qDebug();
if(as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
audioStream = i; //
qDebug()<<"sample_rate="<<as->codecpar->sample_rate;
qDebug()<<"Channels="<<as->codecpar->channels;
}
else if(as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStream = i; //
qDebug()<<"width="<<as->codecpar->width;
qDebug()<<"height="<<as->codecpar->height;
qDebug()<<"video fps="<<FractionToDouble(as->avg_frame_rate);
}
}
//找到视频解码器,创建并打开解码器
AVCodec* vcodec = avcodec_find_decoder(avfc->streams[videoStream]->codecpar->code_id);
if (vcodec)
{
qDebug()<<"video Can't find the video codec";
}
//上下文
AVCodecCOntext* vc = avcodec_alloc_context3(vcodec);
//参数
ret = avcodec_parameters_to_context(vc,avfc->streams[videoStream]->codecpar);
if (ret < 0)
{
qDebug()<<"video avcodec_parameters_to_context failed";
}
//8线程同时解码
vc->thread_count = 8;
avcodec_open2(vc,NULL,NULL);
if (ret < 0)
{
qDebug()<<"video avcodec_open2 failed";
}
//找到音频解码器,创建并打开解码器
AVCodec* acodec = avcodec_find_decoder(avfc->streams[audiooStream]->codecpar->code_id);
if (acodec)
{
qDebug()<<"audio Can't find the video codec";
}
//上下文
AVCodecCOntext* ac = avcodec_alloc_context3(acodec);
//参数
ret = avcodec_parameters_to_context(ac,avfc->streams[audiooStream]->codecpar);
if (ret < 0)
{
qDebug()<<"audio avcodec_parameters_to_context failed";
}
//8线程同时解码
ac->thread_count = 8;
avcodec_open2(ac,NULL,NULL);
if (ret < 0)
{
qDebug()<<"audio accodec_open2 failed";
}
//Packet,Frame
AVPacket* pkt = av_packet_alloc(); //using av_packet_free()
AVFrame* frame = av_frame_alloc(); //using av_frame_free()
ret = 0;
while(ret >= 0)
{
ret = av_read_frame(avfc,pkt); //一帧帧的读
qDebug()<<"pkt->size="<<pkt->size;
qDebug()<<"pkt->pts="<<pkt->pts;
qDebug()<<"pkt->dts="<<pkt->dts;
//完成解码操作
AVCodecContext* cc = NULL;
if(pkt->stream_index == videoStream)
{
qDebug()<<"视频数据";
cc = vc;
}else if(pkt->stream_index == audeoStream)
{
qDebug()<<"音频数据";
cc = ac;
}
int ret2 = avcodec_send_packet(cc,pkt);//以frame形式输出
av_packet_unref(pkt);//引用计数-1
if(ret2 != 0)
{
qDebug<<"Send packet failed";
}
//接受frame
while(true)
{
//有可能多处接受decode发过来的frame
int ret3 = avcodec_receive_frame(cc,frame);
if(ret3 != 0)
{
break;
}
qDebug<<"recv frame"<<frame->format<<" "<<frame->linesize[0];
}
}
//释放资源
av_frame_free(&frame);
av_packet_free(&pkt);
if(avfc)
{
avformat_close_input(&avfc);
}
avfc = NULL;
return a.exec();
}