1.xdemux
图片来源:https://jiedi.ke.qq.com/
int main(int argc, char *argv[])
{
xdemux demux;
const char* path = "E:\\ffmpeg\\test.flv";
const char* url = "rtmp://192.168.1.10/live?vhost=ossrs.net/livestream'<br>url6 = ";
//url = "v1080.mp4";
cout << "demux.Open = " << demux.Open(path)<<endl;
cout << "CopyVPara = " << demux.CopyVPara( ) << endl;
cout << "CopyAPara = " << demux.CopyAPara() << endl;
for (;;)
{
AVPacket* pkt = demux.read();
if (!pkt)break;
}
QApplication a(argc, argv);
Xplay2 w;
w.show();
return a.exec();
}
xdemux.h
#pragma once
#include <mutex>
struct AVFormatContext;
struct AVPacket;
struct AVCodecParameters;
class xdemux
{
public:
//打开媒体文件,或者流媒体 rtmp http rstp
virtual bool Open(const char* url);
xdemux();
virtual ~xdemux();
virtual AVPacket* read();
//媒体总时长(毫秒)
int totalMs = 0;
protected:
std::mutex mux;
//解封装上下文
AVFormatContext* ic = NULL;
//音视频索引,读取时区分音视频
int videoStream = 0;
int audioStream = 1;
};
xdemux.cpp
#include "XDemux.h"
#include <iostream>
#include<mutex>
extern "C" {
#include "libavformat/avformat.h"
}
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
using namespace std;
static double r2d(AVRational r)
{
return r.den == 0 ? 0 : (double)r.num / (double)r.den;
}
xdemux::xdemux()
{
static bool isFirst = true;
mutex mux;
mux.lock();
if (isFirst)
{
//初始化封装库 被弃用了
//av_register_all();
//初始化网络库 (可以打开rtsp rtmp http 协议的流媒体视频)
avformat_network_init();
isFirst = false;
}
mux.unlock();
}
xdemux:: ~xdemux()
{
}
通过实例化xdemux的对象调用xdemux的构造函数,在这里面会进行网络库等初始化
bool xdemux::Open(const char* url)
{
//参数设置
AVDictionary* opts = NULL;
//设置rtsp流已tcp协议打开
av_dict_set(&opts, "rtsp_transport", "tcp", 0);
//网络延时时间
av_dict_set(&opts, "max_delay", "500", 0);
mux.lock();
int re = avformat_open_input(
&ic,
url,
0, // 0表示自动选择解封器
&opts //参数设置,比如rtsp的延时时间
);
if (re != 0)
{
mux.unlock();
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);
cout << "open " << url << " failed! :" << buf << endl;
return false;
}
cout << "open " << url << " success! " << endl;
//获取流信息
re = avformat_find_stream_info(ic, 0);
//总时长 毫秒
int totalMs = ic->duration / (AV_TIME_BASE / 1000);
cout << "totalMs = " << totalMs << endl;
//打印视频流详细信息
av_dump_format(ic, 0, url, 0);
//获取视频流
videoStream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVStream* as = ic->streams[videoStream];
cout << "=======================================================" << endl;
cout << videoStream << "视频信息" << endl;
cout << "codec_id = " << as->codecpar->codec_id << endl;
cout << "format = " << as->codecpar->format << endl;
cout << "width=" << as->codecpar->width << endl;
cout << "height=" << as->codecpar->height << endl;
//帧率 fps 分数转换
cout << "video fps = " << r2d(as->avg_frame_rate) << endl;
cout << "=======================================================" << endl;
cout << audioStream << "音频信息" << endl;
//获取音频流
audioStream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
as = ic->streams[audioStream];
cout << "codec_id = " << as->codecpar->codec_id << endl;
cout << "format = " << as->codecpar->format << endl;
cout << "sample_rate = " << as->codecpar->sample_rate << endl;
cout << "channels = " << as->codecpar->channels << endl;
cout << "frame_size = " << as->codecpar->frame_size << endl;
//1024 * 2 * 2 = 4096 fps =sample_rate/frame_size
mux.unlock();
return true;
}
2.xdemux:: read()
然后通过xdemux的open方法去解封装,在这个函数中将解封装后的信息放到ic中
解封装后要去读每一个packet:AVPacket* pkt = demux.read();
AVPacket* xdemux:: read()
{
mux.lock();
if (!ic)//打开的话ic是0,否则是1
//做容错,虽然在main函数中看起来是read在初始化之后被调用,但假如初始化很慢,会多进程调用read,这时候ic就是空的
{
mux.unlock();
return 0;
}
读和打开不能同时进行,ic在读时初始化,若读一半ic变了的话会发生错误
if (!ic)//打开的话ic是0,否则是1
做容错,虽然在main函数中看起来是read在初始化之后被调用,但假如初始化很慢,会多进程调用read,这时候ic就是空的
AVPacket* pkt=av_packet_alloc();//只是对象空间,并没有分配数据空间
int re=av_read_frame(ic, pkt);//分配数据空间
if (re!=0)//打开的话re是0,否则为负
{
mux.unlock();
av_packet_free(&pkt);
return 0;
}
//为了后面做同步,pts转换为毫秒
pkt->pts = pkt->pts * (1000 * (r2d(ic->streams[pkt->stream_index]->time_base)));
pkt->dts = pkt->dts * (1000 * (r2d(ic->streams[pkt->stream_index]->time_base)));
mux.unlock();
cout << " " << pkt->dts << flush;
return pkt;
}
将解封装后的信息以ic做为桥梁传进int re=av_read_frame(ic, pkt);将读出的信息放在AVPacket中并用pkt指向(标记)出这块空间,读完一帧会自动读下一帧
下面显示的是dts(解码时间戳),因为是通过packet打印的,每个packet里有很多帧的信息,所有每个时间戳之间存在一定的间隔
3.avcodecparameters
解封装获得的信息需要传给decode()
结构体AVCodecParameters中有个指针uint8_t *extradata,
*Extra binary data needed for initializing the decoder, codec - dependent.
*Must be allocated with av_malloc() and will be freed by
avcodec_parameters_free().
//结构体中其他成员可以直接复制,但当结构体释放的时候指针指向的成员也会被释放,直接复制为浅拷贝,就会宕掉
//动态链接库中申请和释放
AVCodecParameters* xdemux::CopyVPara()
{
mux.lock();//用ic了
if (!ic)//可能没打开
{
mux.unlock();
return NULL;
}
AVCodecParameters* pa = avcodec_parameters_alloc();
avcodec_parameters_copy(ic->streams[videoStream]->codecpar, pa);
mux.unlock();
return pa;
}
AVCodecParameters* xdemux::CopyAPara()
{
mux.lock();
if (!ic)//可能没打开
{
mux.unlock();
return NULL;
}
AVCodecParameters* pa = avcodec_parameters_alloc();
avcodec_parameters_copy(ic->streams[audioStream]->codecpar, pa);
return pa;
}
复制视频和音频信息
先做容错判断,
再开辟空间
再调用复制函数,将ic中的信息放到pa中
主函数调用方法:
cout << "CopyVPara = " << demux.CopyVPara( ) << endl;
cout << "CopyAPara = " << demux.CopyAPara() << endl;
打印了pa的地址