1.构造函数
Decode::Decode(DataInfo*dataInfo, QObject*parent): QObject{parent} { mDataInfo=dataInfo; }
2.解码逻辑
voidDecode::slotDecode() { autoformatCtx=mDataInfo->getFormatCtx(); autovideoCodecCtx=mDataInfo->getVideoCtx(); autoaudioCodecCtx=mDataInfo->getAudioCtx(); autoswrCtx=mDataInfo->getSwrCtx(); AVSampleFormatsampleFmt=mDataInfo->getSampleFmt(); intvideoIndex=mDataInfo->getVideoIndex(); intaudioIndex=mDataInfo->getAudioIndex(); //设置正在播放mDataInfo->setDecodeState(true); //设置音频时间为0mDataInfo->setAudioClock(0); //释放音视频队列mDataInfo->dataListRelease(); //av_packet_alloc是一个函数,定义在libavcodec/avpacket.c中。//它用于分配一个AVPacket结构体,并将其字段初始化为默认值。//AVPacket结构体是FFmpeg中用于存储音视频数据包信息的数据结构。//av_packet_alloc函数首先使用av_mallocz分配了一个AVPacket结构体,并将其内存清零。//然后,它调用get_packet_defaults函数设置AVPacket结构体的字段的默认值,//例如pts(显示时间戳),dts(解码时间戳)和pos(数据在文件中的位置)等。//最后,它返回分配的AVPacket结构体指针。另外,av_packet_alloc函数的实现也在av_packet_clone函数中被调用。//在av_packet_clone函数中,它首先调用av_packet_alloc函数分配一个AVPacket结构体,//然后使用av_packet_ref函数将源AVPacket结构体的字段值复制到新分配的AVPacket结构体中。如果复制失败,//它会释放分配的AVPacket结构体并返回NULL。否则,它返回新分配的AVPacket结构体指针。//总而言之,av_packet_alloc函数是用于分配和初始化AVPacket结构体的函数,它在FFmpeg中被广泛使用来处理音视频数据包。//AVPacket是FFmpeg中很重要的一个数据结构,它保存了解复用之后,解码之前的数据(仍然是压缩后的数据)//和关于这些数据的一些附加信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等。//对于视频(Video)来说,AVPacket通常包含一个压缩的Frame,//而音频(Audio)则有可能包含多个压缩的Frame。//并且,一个Packet有可能是空的,不包含任何压缩数据,只含有side data(side data,容器提供的关于Packet的一些附加信息。例如,在编码结束的时候更新一些流的参数)。//AVPacket的大小是公共的ABI(public ABI)一部分,这样的结构体在FFmpeg很少,由此也可见AVPacket的重要性。它可以被分配在栈空间上(可以使用语句AVPacket packet; 在栈空间定义一个Packet ),并且除非libavcodec 和 libavformat有很大的改动,不然不会在AVPacket中添加新的字段。AVPacket*packet=av_packet_alloc(); //AVFrame是一个存储解码后的原始数据的结构体。//它可以通过调用av_frame_alloc函数来构造一个默认配置的AVFrame对象,//并且这个函数只分配AVFrame对象本身,而不分配数据缓冲区。为了根据AVFrame属性分配缓冲区,//可以调用av_frame_get_buffer函数。//该函数会填充AVFrame.data、AVFrame.linesize、AVFrame.buf数组,//并且还会分配和填充AVFrame.extended_data和AVFrame.extended_buf。//对于planar格式,会为每个plane分配一个缓冲区。在使用完AVFrame后,//可以调用av_frame_free函数来释放AVFrame对象和它的所有缓冲区,包括extended_data。//如果AVFrame还被引用计数,则会解除引用。总的来说,AVFrame在解码中是解码器的输出,//在编码中是编码器的输入,它存储了解码或编码后的原始数据。AVFrame*frame=av_frame_alloc(); //av_read_frame是FFmpeg中的一个函数,用于从视频文件中读取一帧压缩数据。//它接受两个参数:AVFormatContext *s和AVPacket *pkt。//AVFormatContext *s是一个表示文件格式上下文的结构体,用于存储文件的相关信息,包括流、编码器等。//AVPacket *pkt是一个用于存储读取到的压缩数据的结构体。// av_read_frame函数的作用是从文件中读取一帧数据,//并将其存储在AVPacket结构体中。它会保证获取的数据是完整的一帧,不会出现半帧的情况。//对于视频,每个AVPacket结构体中只包含一帧数据;而对于音频,如果每帧的大小是固定的,//那么一个AVPacket结构体中可能包含多帧数据,如果每帧大小是可变的,那么一个AVPacket结构体中只包含一帧数据。//av_read_frame函数会返回一个整数值,如果返回值为0,则表示读取成功;如果返回值小于0,//则表示出现了错误或已经到达文件末尾。使用av_read_frame函数时,需要确保传入的AVPacket结构体不为空,且有足够的空间来存储读取到的数据。//在不再需要AVPacket结构体时,需要使用av_packet_unref函数来释放其占用的内存。//总结来说,av_read_frame函数的作用是从视频文件中读取一帧压缩数据,//并将其存储在AVPacket结构体中,以供后续的解码和处理操作使用。//返回值 0 表示成功,其他的异常值说明://AVERROR(EAGAIN):当前不接受输出,必须重新发送//AVERROR_EOF:已经刷新×××,没有新的包可以被刷新//AVERROR(EINVAL):没有打开×××,或者这是一个编码器,或者要求刷新//AVERRO(ENOMEN):无法添加包到内部队列while (av_read_frame(formatCtx, packet) >=0) { //读取一帧if (packet->stream_index==videoIndex) { //视频流//avcodec_send_packet函数用于向解码器发送数据包,//而avcodec_receive_frame函数用于接收解码后的帧数据。//具体来说,avcodec_send_packet函数将包含待解码数据的AVPacket发送给解码器。//它是解码过程的第一步,用于将数据传递给解码器进行解码。//而avcodec_receive_frame函数则用于从解码器中接收已解码的帧数据。//它是解码过程的第二步,用于获取经过解码器处理后的音视频帧数据,以便进行后续的处理和播放。//在使用这两个函数时,通常会使用一个循环结构,不断调用avcodec_receive_frame函数,//直到获取到完整的解码数据或者解码结束。//总结来说,avcodec_send_packet函数用于发送待解码数据包,//而avcodec_receive_frame函数用于接收解码后的帧数据。这两个函数在解码过程中扮演了重要的角色。intret=avcodec_send_packet(videoCodecCtx, packet); //发送帧给解码器//0<送入数据包失败while (ret>=0) { ret=avcodec_receive_frame(videoCodecCtx, frame); //接收解码之后的结果if (ret<0||ret==AVERROR(EAGAIN) ||ret==AVERROR_EOF) break; //AV_NOPTS_VALUE是FFmpeg中定义的一个常量,//表示不可用的时间戳值。在音视频编解码中,//时间戳用于标识音视频帧的播放顺序和时序关系。//AV_NOPTS_VALUE通常用于表示暂时无法获取到时间戳的情况,//例如在某个音视频帧的解码过程中,无法确定其准确的时间戳值时,//可以使用AV_NOPTS_VALUE来代表该帧的时间戳。if (AV_NOPTS_VALUE!=frame->pts) mDataInfo->videoPush(frame, frame->pts*1000*av_q2d(formatCtx->streams[videoIndex]->time_base)); elsemDataInfo->videoPush(frame, 0); } } if (packet->stream_index==audioIndex) { //音频流intret=avcodec_send_packet(audioCodecCtx, packet); //发送帧给解码器while (ret>=0) { ret=avcodec_receive_frame(audioCodecCtx, frame); //接收解码之后的结果if (ret<0||ret==AVERROR(EAGAIN) ||ret==AVERROR_EOF) break; //转换音频格式//av_malloc是FFmpeg库中的一个函数,用于在堆上分配内存空间。它的函数原型如下://void *av_malloc(size_t size);//void *av_malloc(size_t size);//该函数接受一个size参数,表示要分配的内存大小(以字节为单位),并返回一个指向分配内存空间的指针。如果分配成功,则返回的指针指向已分配的内存块的起始位置;如果分配失败,则返回NULL。//在使用完分配的内存后,应使用av_free函数释放该内存,以防止内存泄漏。av_free的函数原型如下:// void av_free(void *ptr);//void av_free(void *ptr);//该函数接受一个ptr参数,表示要释放的内存块的起始地址。使用av_free释放内存后,应确保不再使用该指针。uint8_t*buff= (uint8_t*)(av_malloc(2*audioCodecCtx->sample_rate)); //swr_convert 是 FFmpeg 库中的一个函数,用于音频重采样。//它可以将音频数据从一种采样格式转换为另一种采样格式,例如将 PCM 音频数据从一个采样率、声道数和格式转换为另一个采样率、声道数和格式。该函数可以在音频处理和转码等场景中使用。//参数1:音频重采样的上下文//参数2:输出的指针。传递的输出的数组//参数3:输出的样本数量,不是字节数。单通道的样本数量。//参数4:输入的数组,AVFrame解码出来的DATA//参数5:输入的单通道的样本数量。swr_convert(swrCtx, &buff, 2*audioCodecCtx->sample_rate, (constuint8_t**)frame->data, frame->nb_samples); //av_samples_get_buffer_size是一个函数,用于计算采样数所需的缓冲区大小。这个函数的实现可以在samplefmt.c中找到。函数的输入参数包括每行的大小(linesize)、声道数(nb_channels)、采样数(nb_samples)、采样格式(sample_fmt)和对齐方式(align)。函数首先检查参数是否合法,然后根据参数计算缓冲区的大小。intbuffSize=av_samples_get_buffer_size(nullptr, audioCodecCtx->ch_layout.nb_channels, frame->nb_samples, sampleFmt, 1); if (AV_NOPTS_VALUE!=frame->pts) mDataInfo->audioPush(buffSize, buff, frame->pts*1000*av_q2d(formatCtx->streams[audioIndex]->time_base)); elsemDataInfo->audioPush(buffSize, buff, 0); // av_free(buff); 播放音频时释放内存 } } av_packet_unref(packet); //音视频有一个为空while (mDataInfo->videoIsFull() ||mDataInfo->audioIsFull()) { //暂停状态if (mDataInfo->getPlayState() ==PLAY_STOP) break; //跳转时间大于等于0或者跳转不等于正常if (mDataInfo->getSeekTime() >=0||mDataInfo->getSeekForward() !=SEEK_NORMAL) { if (!mDataInfo->videoIsEmpty() ||!mDataInfo->audioIsEmpty()) mDataInfo->dataListRelease(); int64_tplayTime=0; int64_tseekTime=mDataInfo->getSeekTime(); if (seekTime>=0) { //如果大于总长度if (seekTime>mDataInfo->getLength()) seekTime=mDataInfo->getLength(); mDataInfo->setSeekTime(-1); } else { //后退if (mDataInfo->getSeekForward() ==SEEK_BACKWARD) { playTime=mDataInfo->getPlayTime(); seekTime=playTime<=5000?0 : playTime-5000; } //前进if (mDataInfo->getSeekForward() ==SEEK_FORWARD) { playTime=mDataInfo->getPlayTime() +5000; seekTime=playTime<=mDataInfo->getLength() ?playTime : mDataInfo->getLength(); } mDataInfo->setSeekForward(SEEK_NORMAL); } if (seekTime>=0) { //int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);//参数://s: AVFormatContext 指针。包含了流媒体的信息。//stream_index: 流索引,流即视频流,音频流等,视频流索引为0,音频流索引为1 。-1:表示默认流。//timestamp: 时间戳 要定位的时间戳位置,int64_t类型的时间戳,表示要跳转到的时间位置//flags:seek标志,有以下四种://AVSEEK_FLAG_BACKWARD 是seek到请求的时间戳之前最近的关键帧//AVSEEK_FLAG_BYTE 是基于字节位置的查找,精确到字节 mp4格式不能使用该标识会seek失败//AVSEEK_FLAG_ANY 是可以seek到任意帧,不一定是关键帧,可能是p帧,b帧,因此使用时可能会导致花屏 flv格式不能使用该标识会seek失败//AVSEEK_FLAG_FRAME 是基于帧数量快进av_seek_frame(formatCtx, videoIndex, (int64_t)(seekTime/av_q2d(formatCtx->streams[videoIndex]->time_base) /1000.0), AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME); mDataInfo->setSeekFlag(true); break; } } QThread::msleep(5); } if (mDataInfo->getSeekTime() >=0||mDataInfo->getSeekForward() !=SEEK_NORMAL) { if (!mDataInfo->videoIsEmpty() ||!mDataInfo->audioIsEmpty()) mDataInfo->dataListRelease(); int64_tplayTime=0; int64_tseekTime=mDataInfo->getSeekTime(); if (seekTime>=0) { if (seekTime>mDataInfo->getLength()) seekTime=mDataInfo->getLength(); mDataInfo->setSeekTime(-1); } else { if (mDataInfo->getSeekForward() ==SEEK_BACKWARD) { playTime=mDataInfo->getPlayTime(); seekTime=playTime<=5000?0 : playTime-5000; } if (mDataInfo->getSeekForward() ==SEEK_FORWARD) { playTime=mDataInfo->getPlayTime() +5000; seekTime=playTime<=mDataInfo->getLength() ?playTime : mDataInfo->getLength(); } mDataInfo->setSeekForward(SEEK_NORMAL); } if (seekTime>=0) { av_seek_frame(formatCtx, videoIndex, (int64_t)(seekTime/av_q2d(formatCtx->streams[videoIndex]->time_base) /1000.0), AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME); mDataInfo->setSeekFlag(true); } } if (mDataInfo->getPlayState() ==PLAY_STOP) break; } mDataInfo->setDecodeState(false); av_frame_free(&frame); av_packet_free(&packet); }