头文件:
xformat.h
#pragma once
/// 封装和解封装基类
#include <mutex>
struct AVFormatContext;
struct AVCodecParameters;
struct AVPacket;
struct XRational
{
int num; ///< Numerator
int den; ///< Denominator
};
class XFormat
{
public:
/// <summary>
/// 复制参数 线程安全
/// </summary>
/// <param name="stream_index">对应c_->streams 下标</param>
/// <param name="dst">输出参数</param>
/// <returns>是否成功</returns>
bool CopyPara(int stream_index, AVCodecParameters* dst);
/// <summary>
/// 设置上下文,并且清理上次的设置的值,如果传递NULL,相当于关闭上下文3
/// 线程安全
/// </summary>
/// <param name="c"></param>
void set_c(AVFormatContext* c);
int audio_index() { return audio_index_; }
int video_index() { return video_index_; }
XRational video_time_base(){ return video_time_base_; }
XRational audio_time_base() { return audio_time_base_; }
protected:
AVFormatContext* c_ = nullptr; //封装解封装上下文
std::mutex mux_; //c_ 资源互斥
int video_index_ = 0;//video和audio在stream中索引
int audio_index_ = 1;
XRational video_time_base_ = {1,25};
XRational audio_time_base_ = {1,9000};
};
xdemux.h
#pragma once
#include "xformat.h"
class XDemux :public XFormat
{
public:
/// <summary>
/// 打开解封装
/// </summary>
/// <param name="url">解封装地址 支持rtsp</param>
/// <returns>失败返回nullptr</returns>
static AVFormatContext* Open(const char* url);
/// <summary>
/// 读取一帧数据
/// </summary>
/// <param name="pkt">输出数据</param>
/// <returns>是否成功</returns>
bool Read(AVPacket* pkt);
};
xmux.h
#pragma once
#include "xformat.h"
//
/// 媒体封装
class XMux :public XFormat
{
public:
//
打开封装
static AVFormatContext* Open(const char* url);
bool WriteHead();
bool Write(AVPacket* pkt);
bool WriteEnd();
};
源文件:
main.cpp
#include <iostream>
#include <thread>
#include "xdemux.h"
#include "xmux.h"
using namespace std;
extern "C" { //指定函数是c语言函数,函数名不包含重载标注
//引用ffmpeg头文件
#include <libavformat/avformat.h>
}
//预处理指令导入库
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
void PrintErr(int err)
{
char buf[1024] = { 0 };
av_strerror(err, buf, sizeof(buf) - 1);
cerr << endl;
}
#define CERR(err) if(err!=0){ PrintErr(err);getchar();return -1;}
int main(int argc, char* argv[])
{
//打开媒体文件
const char* url = "v1080.mp4";
/// 解封装
//解封装输入上下文
XDemux demux;
auto demux_c = demux.Open(url);
demux.set_c(demux_c);
/// 封装
//编码器上下文
const char* out_url = "test_mux.mp4";
XMux mux;
auto mux_c = mux.Open(out_url);
mux.set_c(mux_c);
auto mvs = mux_c->streams[mux.video_index()]; //视频流信息
auto mas = mux_c->streams[mux.audio_index()]; //视频流信息
//有视频
if (demux.video_index() >= 0)
{
mvs->time_base.num = demux.video_time_base().num;
mvs->time_base.den = demux.video_time_base().den;
//复制视频参数
demux.CopyPara(demux.video_index(), mvs->codecpar);
}
//有音频
if (demux.audio_index() >= 0)
{
mas->time_base.num = demux.audio_time_base().num;
mas->time_base.den = demux.audio_time_base().den;
//复制音频参数
demux.CopyPara(demux.audio_index(), mas->codecpar);
}
mux.WriteHead();
/// 截取10 ~ 20 秒之间的音频视频 取多不取少
// 假定 9 11秒有关键帧 我们取第9秒
double begin_sec = 10.0; //截取开始时间
double end_sec = 20.0; //截取结束时间
long long begin_pts = 0;
long long begin_audio_pts = 0; //音频的开始时间
long long end_pts = 0;
AVPacket pkt;
for (;;)
{
if (!demux.Read(&pkt))
{
break;
}
pkt.pos = -1;
//写入音视频帧 会清理pkt
mux.Write(&pkt);
}
//写入结尾 包含文件偏移索引
mux.WriteEnd();
/*re = av_write_trailer(ec);
if (re != 0)PrintErr(re);*/
//avformat_close_input(&ic);
demux.set_c(nullptr);
mux.set_c(nullptr);
getchar();
return 0;
}
xformat.cpp
#include "xformat.h"
#include <iostream>
#include <thread>
using namespace std;
extern "C" { //指定函数是c语言函数,函数名不包含重载标注
//引用ffmpeg头文件
#include <libavformat/avformat.h>
}
//预处理指令导入库
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
using namespace std;
void XFormat::set_c(AVFormatContext* c)
{
unique_lock<mutex> lock(mux_);
if (c_) //清理原值
{
if (c_->oformat) //输出上下文
{
if (c_->pb)
avio_closep(&c_->pb);
avformat_free_context(c_);
}
else if (c_->iformat) //输入上下文
{
avformat_close_input(&c_);
}
else
{
avformat_free_context(c_);
}
}
c_ = c;
if (!c_)return;
//用于区分是否有音频或者视频流
audio_index_ = -1;
video_index_ = -1;
//区分音视频stream 索引
for (int i = 0; i < c->nb_streams; i++)
{
//音频
if (c->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
audio_index_ = i;
audio_time_base_.den = c->streams[i]->time_base.den;
audio_time_base_.num = c->streams[i]->time_base.num;
}
else if (c->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
video_index_ = i;
video_time_base_.den = c->streams[i]->time_base.den;
video_time_base_.num = c->streams[i]->time_base.num;
}
}
}
/// <summary>
/// 复制参数 线程安全
/// </summary>
/// <param name="stream_index">对应c_->streams 下标</param>
/// <param name="dst">输出参数</param>
/// <returns>是否成功</returns>
bool XFormat::CopyPara(int stream_index, AVCodecParameters* dst)
{
unique_lock<mutex> lock(mux_);
if (!c_)
{
return false;
}
if (stream_index<0 || stream_index>c_->nb_streams)
return false;
auto re = avcodec_parameters_copy(dst, c_->streams[stream_index]->codecpar);
if (re < 0)
{
return false;
}
return true;
}
xdemux.cpp
#include "xdemux.h"
#include <iostream>
#include <thread>
using namespace std;
extern "C" { //指定函数是c语言函数,函数名不包含重载标注
//引用ffmpeg头文件
#include <libavformat/avformat.h>
}
static void PrintErr(int err)
{
char buf[1024] = { 0 };
av_strerror(err, buf, sizeof(buf) - 1);
cerr << buf << endl;
}
#define BERR(err) if(err!= 0){PrintErr(err);return 0;}
AVFormatContext* XDemux::Open(const char* url)
{
AVFormatContext* c = nullptr;
//打开封装上下文
auto re = avformat_open_input(&c, url, nullptr, nullptr);
BERR(re);
//获取媒体信息
re = avformat_find_stream_info(c, nullptr);
BERR(re);
//打印输入封装信息
av_dump_format(c, 0, url, 0);
return c;
}
bool XDemux::Read(AVPacket* pkt)
{
unique_lock<mutex> lock(mux_);
if (!c_)return false;
auto re = av_read_frame(c_, pkt);
BERR(re);
return true;
}
xmux.cpp
#include "xmux.h"
#include <iostream>
#include <thread>
using namespace std;
extern "C" { //指定函数是c语言函数,函数名不包含重载标注
//引用ffmpeg头文件
#include <libavformat/avformat.h>
}
static void PrintErr(int err)
{
char buf[1024] = { 0 };
av_strerror(err, buf, sizeof(buf) - 1);
cerr << buf << endl;
}
#define BERR(err) if(err!= 0){PrintErr(err);return 0;}
//
打开封装
AVFormatContext* XMux::Open(const char* url)
{
AVFormatContext* c = nullptr;
//创建上下文
auto re = avformat_alloc_output_context2(&c, NULL, NULL, url);
BERR(re);
//添加视频音频流
auto vs = avformat_new_stream(c, NULL); //视频流
vs->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
auto as = avformat_new_stream(c, NULL); //音频流
as->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
//打开IO
re = avio_open(&c->pb, url, AVIO_FLAG_WRITE);
BERR(re);
return c;
}
bool XMux::Write(AVPacket* pkt)
{
unique_lock<mutex> lock(mux_);
if (!c_)return false;
//写入一帧数据,内部缓冲排序dts,通过pkt=null 可以写入缓冲
auto re = av_interleaved_write_frame(c_,pkt);
BERR(re);
return true;
}
bool XMux::WriteEnd()
{
unique_lock<mutex> lock(mux_);
if (!c_)return false;
av_interleaved_write_frame(c_, nullptr);//写入排序缓冲
auto re = av_write_trailer(c_);
BERR(re);
return true;
}
bool XMux::WriteHead()
{
unique_lock<mutex> lock(mux_);
if (!c_)return false;
auto re = avformat_write_header(c_, nullptr);
BERR(re);
//打印输出上下文
av_dump_format(c_, 0, c_->url, 1);
return true;
}
运行结果:
重新生成了一个名字为test_mux.mp4文件