方法声明
extern "C" //ffmpeg使用c语言实现的,引入用c写的代码就要用extern
{
#include <libavcodec/avcodec.h> //注册
#include <libavdevice/avdevice.h> //设备
#include <libavformat/avformat.h>
#include <libavutil/error.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
#include<iostream>
class fcoverh264
{
public:
fcoverh264(int number);
//打开H264视频文件
void openFile(std::string file);
//根据我们需要的封装格式进行处理
void outPut(std::string fileout);
private:
AVFormatContext* forContext, * formatout;//保存数据的结构体 forContext存输入进来的视频信息;formatout存储最终输出的视频信息
AVPacket* pkt;//pkt
int videoType;
};
定义实现
#include "fcoverh264.h"
extern "C" //ffmpeg使用c语言实现的,引入用c写的代码就要用extern
{
#include <libavcodec/avcodec.h> //注册
#include <libavdevice/avdevice.h> //设备
#include <libavformat/avformat.h>
#include <libavutil/error.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
fcoverh264::fcoverh264(int number):videoType(number)
{
/*
* 转码的流程:
* 1.注册组件
* 2.打开视频流 打开视频文件
* 3.查找有没有流数据
* 4.查找视频码流数据
*
* 6.根据要的封装格式 来猜测格式对应编辑器
* 7.打开对应文件
* 8.新建流
* 9.写入头部信息
* 10.读取一帧一帧的码流数据
* 11.转码---->时间基的转化
* 所以在解码的时候:显示顺序和解码的顺序是一样的;
处理其他视频的时候:就需要关注 显示顺序和解码的顺序是否一致了
编码有B帧? 解码:IPB
* 12.写入对应的一帧数据到文件中
*/
//注册组件
av_register_all();
forContext = avformat_alloc_context();
}
void fcoverh264::openFile(std::string file)
{
//打开输入视频
int res = avformat_open_input(&forContext, file.c_str(), nullptr, nullptr);
//判断是否打开成功
if (res < 0)
{
std::cout << "打开失败"<<std::endl;
return;
}
//打开视频文件成功,获取文件信息
res = avformat_find_stream_info(forContext, nullptr);//查看有没有相关视频流信息
if (res < 0)//判断是否有流媒体
{
std::cout << "没有流媒体信息" << std::endl;
return;
}
//一个视频流有多股码流,存在forContentext中streams数组中
/*int videoType = -1;*/
for (int i = 0; i < forContext->nb_streams; i++) //i小于流的个数
{
if (forContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)//视频流
{
videoType = i;//标识类型
break;
}
}
if (videoType == -1)
{
std::cout << "没有视频流相关信息" << std::endl;
return;
}
std::cout << "输入的准备已经完成"<<std::endl;
}
//根据我们需要的封装格式进行处理
void fcoverh264::outPut(std::string fileout)
{
//猜测编码器
AVOutputFormat* avformat = av_guess_format(nullptr, fileout.c_str(), nullptr);
if (avformat == nullptr)
{
std::cout << "没有编码器!"<<std::endl;
return;
}
std::cout << "AVOutputFormat"<<std::endl;
//保存输出视频信息的结构体
formatout = avformat_alloc_context();
//设置输出格式
formatout->oformat = avformat;
//打开视频流 文件流
//参数1:输入输出的上下文对象
//参数2:文件流路径
//参数3:文件打开格式 写的方式
int res = avio_open(&formatout->pb, fileout.c_str(), AVIO_FLAG_WRITE);
if (res < 0)
{
std::cout << "open file error"<<std::endl;
return;
}
std::cout << "avio_open"<<std::endl;
//新建视频流
//参数1:视频信息结构体
//参数2:新建流 的 返回新建流 的地址
AVStream* newStream = avformat_new_stream(formatout, nullptr);
if (newStream == nullptr)
{
std::cout << "打开视频流失败"<<std::endl;
return;
}
std::cout << "newStream"<<std::endl;
//编码器对应参数设置 拷贝参数设置 newStream:输入进入流的参数设置
res = avcodec_parameters_copy(newStream->codecpar, forContext->streams[videoType]->codecpar);
std::cout << "res=" << res<<std::endl;
if (res < 0)
{
std::cout << "拷贝失败!"<< std::endl;
return;
}
std::cout << "res=" << res<<std::endl;
//设置新的流里面 codec_tag 设置为0
newStream->codecpar->codec_tag = 0;
//头部信息写入----写入成功与否
res = avformat_write_header(formatout, nullptr);//formatout封装格式的结构体
//判断写入成功与否
if (res < 0)
{
std::cout << "写入头部信息失败!" <<std::endl;
return;
}
std::cout << "res=" << res << std::endl;
//开始读取码流数据
pkt = (AVPacket*)malloc(sizeof(AVPacket));
//算出这张图有多大
int size = newStream->codecpar->width * newStream->codecpar->height;
av_new_packet(pkt, size);
int frameCount = 0;
//一帧一帧的读取
while (av_read_frame(forContext, pkt) == 0)
{
//判断这一帧这是不是视频流
if (pkt->stream_index == videoType)
{
frameCount++;
//如果是视频流----判断有没有设置过 时间基
if (pkt->pts == AV_NOPTS_VALUE)
{
//时间基 time_base AVRational属性
AVRational timebase = forContext->streams[videoType]->time_base;
//计算帧之间的长度(duration) double强制转换
int64_t duration = (double)AV_TIME_BASE / av_q2d(forContext->streams[videoType]->r_frame_rate);
//计算显示时间基(pts):公式:(当前帧数*两帧之间的长度))/(输入时间基*AV_TIME_BASE)
pkt->pts = (double)(frameCount * duration) / (av_q2d(timebase) * AV_TIME_BASE);
//解码时间基(dts)
pkt->dts = pkt->pts;
//目标两帧之间的长度
pkt->duration = duration / (double)(av_q2d(timebase) * AV_TIME_BASE);
}
else if (pkt->pts < pkt->dts)//显示 时间基 小于 解码时间基 不要这样子的
{
continue;
}
//上述步骤为 时间基设置
//解码 时间基 真正的转换 如下:
//显示时间基的转换
pkt->pts = av_rescale_q_rnd(pkt->pts, forContext->streams[videoType]->time_base,
newStream->time_base, (AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX));
//解码时间基的转换
pkt->dts = av_rescale_q_rnd(pkt->dts, forContext->streams[videoType]->time_base,
newStream->time_base, (AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX));
//数据时长设置
pkt->duration = av_rescale_q(pkt->duration, forContext->streams[videoType]->time_base,
newStream->time_base);
//数据位置的设置 数据在流信息中的设置
pkt->pos = -1;
//数据包的标记:结合AV_PKT_FLAG_KEY使用 最小为1表示这一帧是一个关键帧
pkt->flags |= AV_PKT_FLAG_KEY;
//标记:当前写入的这一帧是视频流
pkt->stream_index = 0;
//转码后的数据包 写入 目标视频信息 结构体 中
av_interleaved_write_frame(formatout, pkt);
}
//清空处理:重新设置包
av_packet_unref(pkt);
}
//写入尾巴帧
av_write_trailer(formatout);
//用完之后进行 关闭 处理 :关闭猜测完的流
avio_close(formatout->pb);//对应avio_open()
std::cout << "avio_close"<<std::endl;
//释放malloc的空间 释放保存信息的结构体
av_free(formatout);
std::cout << "av_free"<<std::endl;
//关闭输入流
avformat_close_input(&forContext);//对应avformat_open_inpu
std::cout << "avformat_close_input"<<std::endl;
//释放forContext结构体空间
av_free(forContext);
std::cout << "av_free"<<std::endl;
}
调用实例
#include <iostream>
#include "fcoverh264.h"
int main() {
fcoverh264* cover = new fcoverh264{-1}; //转码
cover->openFile("test01.h264");
cover->outPut("code_frame.flv");
return 0;
}