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);
}
相关文章
|
2月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
126 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
18天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
2月前
lua面向对象(类)和lua协同线程与协同函数、Lua文件I/O
Lua的面向对象编程、协同线程与协同函数的概念和使用,以及Lua文件I/O操作的基本方法。
32 4
lua面向对象(类)和lua协同线程与协同函数、Lua文件I/O
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
20 3
|
2月前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
37 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
31 2
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
34 1
|
2月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
31 3
|
2月前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
34 0
|
2月前
|
NoSQL Java Redis
Reactor实战,创建一个简单的单线程Reactor(理解了就相当于理解了多线程的Reactor)
本文通过一个简单的单线程Reactor模式的Java代码示例,展示了如何使用NIO创建一个服务端,处理客户端的连接和数据读写,帮助理解Reactor模式的核心原理。
33 0
Reactor实战,创建一个简单的单线程Reactor(理解了就相当于理解了多线程的Reactor)