1.xdecode.h
class xdecode
{
public:
bool isAudio=false;
//pa指向空间的参数传给解码器后这个空间里的数据就没用了,需要释放这个空间,不释放会内存泄漏,
//在该函数所有返回之前都要释放(成功与错误都需要释放)
virtual bool open(AVCodecParameters* pa);
virtual void close();
virtual void clear();
//将packet发给解码缓冲队列,并且清空pkt空间(对象与媒体内容)
virtual bool send(AVPacket*pkt);
//接收解码缓冲队列中的解码好的帧,一次send对应多次receive,send结束了之后还要receive确保清空
virtual AVFrame* receive(int&r);
protected:
AVCodecContext* codeccontext = 0;
std::mutex mux;
};
和demux类一样,将mux锁与上下文变量设置为protected
2.main函数中如何使用
xdecode vdecode;
cout << "vdecode.open()" << vdecode.open(demux.CopyVPara()) << endl;
xdecode adecode;
cout << "adecode.open()" << adecode.open(demux.CopyAPara()) << endl;
int i = 0;
for (;;)
{
AVPacket* pkt = demux.readfz();//读完一个packet自动读下一个
if (vdecode.isAudio == false)
{
cout<<"send"<<vdecode.send(pkt)<<endl;
for (;;)
{
int re = 0;
AVFrame* frame = vdecode.receive(re);
if (re != 0)break;
}
}
if (adecode.isAudio == true)
{
cout << "send" << adecode.send(pkt) << endl;
for (;;)
{
int re = 0;
AVFrame* frame = adecode.receive(re);
if (re != 0)break;
}
}
if (!pkt)break;
}
音频与视频解封装用一个同一个类,解码各用各的类,因此实例化了两个decode,以视频解码器为例:
01.先实例化:xdecode vdecode;
02.再将解封装获得的视频参数复制一份,做为参数传给vdecode:vdecode.open(demux.CopyVPara()) << endl;
03.在解封装的结果中进行读封装,读的是一个个packet,结果放在pkt中:AVPacket* pkt = demux.readfz();
04.将读出的pkt发给刚刚实例化好的解码器vdecode进行解码:vdecode.send(pkt)
05.将解码线程的结果放在frame中:AVFrame* frame = vdecode.receive(re);
3.xdecode.cpp
bool xdecode::open(AVCodecParameters* pa)
{ //容错处理
if (!pa)return false;//pa为空,返回错
close();
if (pa->codec_type == 1)
isAudio = true;
else
isAudio = false;
if(isAudio)
const AVCodec* codec = avcodec_find_decoder(pa->codec_id);
//有可能找不到流中的解码器号,这里要做个判断看看是否找到解码器
if (!codec)
{
avcodec_parameters_free(&pa);
std::cout << "can't find the codec id" <<pa->codec_id << endl;
return false;
}
if (!isAudio)
std::cout << " find the video codec id" << pa->codec_id << endl;
else
std::cout << " find the audio codec id" << pa->codec_id << endl;
mux.lock();
///03创建解码器上下文
codeccontext = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codeccontext, pa);//将解封装得到的信息传给解码上下文
avcodec_parameters_free(&pa);
//八线程解码
codeccontext->thread_count = 8;
///05打开解码器上下文
int re = avcodec_open2(codeccontext, 0, 0);//第二个参数是解码器codec,但在创建上下文的时候已经传进去了,这里就不用再传了
if (re != 0)
{
avcodec_parameters_free(&pa);
mux.unlock();
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf) - 1);//用buf存放错误信息
std::cout << "open audio codec failed!" << buf << endl;
getchar();
return false;
}
std::cout << "video codec success!" << endl;
mux.unlock();
return true;
}
01.根据传进来的codec_id知道是哪种类型的解码器,根据这个id号找到对应解码器:
const AVCodec* codec = avcodec_find_decoder(pa->codec_id);
02.将指向视频解码器的指针传入,为类的解码器上下文成员开辟空间,并设置默认参数:
codeccontext = avcodec_alloc_context3(codec);
03.将解封装得到的解码器信息传给解码器上下文,至此该解码器信息已完成了使命,可以被释放掉了:avcodec_parameters_to_context(codeccontext, pa);
avcodec_parameters_free(&pa);
04.打开解码器上下文,第二个参数是解码器codec,但在创建上下文的时候已经传进去了,这里就不用再传了:
int re = avcodec_open2(codeccontext, 0, 0);
void xdecode::close()
{
mux.lock();
if (codeccontext)
{
avcodec_close(codeccontext);//其他函数中使用到codec的代码段都要加锁
avcodec_free_context(&codeccontext);
}
mux.unlock();
}
解码器上下文codeccontext对于类外不可访问,这里将close功能封装成一个函数
void xdecode::clear()//清理解码缓冲,比读缓冲更大
{
mux.lock();
if (codeccontext)
avcodec_flush_buffers(codeccontext);
mux.unlock();
}
关闭与清理的都是类中的数据成员codeccontext
bool xdecode::send(AVPacket* pkt)
{
if (!pkt || pkt->size <= 0 || !pkt->data)
{
cout << "a" << endl;
return false;
}
mux.lock();
if(!codeccontext)
{
mux.unlock();
cout << "b" << endl;
return false;
}
int re = avcodec_send_packet(codeccontext, pkt);//将pkt给前面打开的codec
mux.unlock();
av_packet_free(&pkt);
if (re != 0)
{
cout << "c" << endl;
return false;
}
return true;
}
将pkt给前面打开的codec:
int re = avcodec_send_packet(codeccontext, pkt);
AVFrame* xdecode::receive(int&r)
{
mux.lock();
if (!codeccontext)
{
mux.unlock();
return NULL;
}
AVFrame* frame = av_frame_alloc();//开辟接收解码队列输出的空间
r = avcodec_receive_frame(codeccontext, frame);
mux.unlock();
if (r != 0)
{
av_frame_free(&frame);//若接收失败的话开辟的接收空间就白开了,要清空掉
return NULL;
}
cout <<"["<< frame->linesize[0]<<"]" << flush;
return frame;
}
01开辟接收解码队列输出的空间:AVFrame* frame = av_frame_alloc();
02根据解码器上下文获得frame:
r = avcodec_receive_frame(codeccontext, frame);
若容错判读失败,则开辟的frame空间没用了,需要清空掉
4.结果
使用flv文件进行测试:
根据打印了a和c,存在两个问题:
01.从解封装中读的packet有问题:
AVPacket* pkt = demux.readfz();
02将pkt给前面打开的codec失败了:
int re = avcodec_send_packet(codeccontext, pkt);
网上看到的以及知识补充:
直接从输入流的已知信息中获取到的关键参数中自己构造一个AVCodecParameters,如此构造后也能正常解码,但是解码的输入流有非常严格的限制:
输入的pkt中data数据必须是以00 00 00 01开头的数据,比如ps流 rtsp rtp 数据就能正常解码,但是如果打开的流是flv MP4等就会出现错误:No start code is found,或者一直返回AVERROR(EAGAIN)
NAL全称Network Abstract Layer, 即网络抽象层。
(https://blog.csdn.net/ygm_linux/article/details/25562921)
在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧……
如下图:
NALU类型是我们判断帧类型的利器,从官方文档中得出如下图:
以00 00 00 01分割之后的下一个字节就是NALU类型,将其转为二进制数据后,解读顺序为从左往右算,如下:
(1)第1位禁止位,值为1表示语法出错
(2)第2~3位为参考级别
(3)第4~8为是nal单元类型
例如上面00000001后有67,68以及65
其中0x67的二进制码为:
0110 0111
4-8为00111,转为十进制7,参考第二幅图:7对应序列参数集SPS,即0x67开始直到下个00000001之间的数据为SPS
最后改好了,问题出在main函数中,不应该判断解码器里的类型,而是应该判断读封装读出packet的类型,将这个判断功能放在demux中实现:
int main(int argc, char *argv[])
{
xdemux demux;
const char* path = "E:\\ffmpeg\\test02.mp4";
cout << "demux.Open = " << demux.Open(path) << endl;
xdecode vdecode;//音视频解封装用一个类,解码各用各的
cout << "vdecode.open()" << vdecode.open(demux.CopyVPara()) << endl;
xdecode adecode;
cout << "adecode.open()" << adecode.open(demux.CopyAPara()) << endl;
for (;;)
{
AVPacket* pkt = demux.readfz();
if (demux.isvideo(pkt) == true)
{
vdecode.send(pkt);
AVFrame* frame = vdecode.receive();
}
else
{
adecode.send(pkt);
AVFrame* frame = adecode.receive();
}
if (!pkt)break;
}
QApplication a(argc, argv);
Xplay2 w;
w.show();
return a.exec();
}
AVPacket是FFmpeg中很重要的一个数据结构,它保存了解复用之后,解码之前的数据(仍然是压缩后的数据)和关于这些数据的一些附加信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等。
对于视频(Video)来说,AVPacket通常包含一个压缩的Frame,而音频(Audio)则有可能包含多个压缩的Frame。并且,一个Packet有可能是空的,不包含任何压缩数据,只含有side data(side data,容器提供的关于Packet的一些附加信息。例如,在编码结束的时候更新一些流的参数)。