播放器实战16 xdecode类

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的一些附加信息。例如,在编码结束的时候更新一些流的参数)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值