ffmpeg播放器实战(解码线程类)

简介: 解码线程类

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);
}
相关文章
|
1月前
|
算法 数据处理 开发者
FFmpeg库的使用与深度解析:解码音频流流程
FFmpeg库的使用与深度解析:解码音频流流程
36 0
|
21天前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
【4月更文挑战第6天】Java中的`synchronized`关键字用于处理多线程并发,确保共享资源的线程安全。它可以修饰方法或代码块,实现互斥访问。当用于方法时,锁定对象实例或类对象;用于代码块时,锁定指定对象。过度使用可能导致性能问题,应注意避免锁持有时间过长、死锁,并考虑使用`java.util.concurrent`包中的高级工具。正确理解和使用`synchronized`是编写线程安全程序的关键。
|
4天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
24 0
|
1天前
|
人工智能 安全 Java
Python 多线程编程实战:threading 模块的最佳实践
Python 多线程编程实战:threading 模块的最佳实践
12 5
|
2天前
|
消息中间件 缓存 NoSQL
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
|
11天前
|
监控 Java 关系型数据库
JVM工作原理与实战(十三):打破双亲委派机制-线程上下文类加载器
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了打破双亲委派机制的方法、线程上下文类加载器等内容。
14 2
|
18天前
|
存储 安全 Java
java多线程之原子操作类
java多线程之原子操作类
|
19天前
|
Java
Java中的多线程实现:使用Thread类与Runnable接口
【4月更文挑战第8天】本文将详细介绍Java中实现多线程的两种方法:使用Thread类和实现Runnable接口。我们将通过实例代码展示如何创建和管理线程,以及如何处理线程同步问题。最后,我们将比较这两种方法的优缺点,以帮助读者在实际开发中选择合适的多线程实现方式。
23 4
|
21天前
|
Java Spring
springboot单类集中定义线程池
该内容是关于Spring中异步任务的配置和使用步骤。首先,在启动类添加`@EnableAsync`注解开启异步支持。然后,自定义线程池类`EventThreadPool`,设置核心和最大线程数、存活时间等参数。接着,将线程池bean注入到Spring中,如`@Bean(&quot;RewardThreadPool&quot;)`。最后,在需要异步执行的方法上使用`@Async`注解,例如在一个定时任务类中,使用`@Scheduled(cron = &quot;...&quot;)`和`@Async`结合实现异步定时任务。
16 2
|
26天前
|
编解码 vr&ar 内存技术
FFmpeg常用命令行讲解及实战一(三)
FFmpeg常用命令行讲解及实战一
40 0

热门文章

最新文章