• 【Qt+FFmpeg】 - FFmpeg解码详细流程


    目录

    一:视频解码流程

    二:FFMPEG解码流程

    三:FFmpeg解码函数

    四:FFmpeg解码的数据结构

    五:FFmpeg数据结构简介

    六:FFmpeg数据结构分析

    七:像素数据转换

    八:FFMPEG解码

    九:FFMPEG解码-视频播放

    一:视频解码流程

    1.1 纯净的视频解码流程

    压缩编码数据->像素数据。例如解码H.264,就是“H.264码流->YUV”。

    1.2 一般的视频解码流程

    视频码流一般存储在一定的封装格式(例如MP4、AVI等)中。

    封装格式中通常还包含音频码流等内容。

    对于封装格式中的视频,需要先从封装格式中提取中视频码流,然后再进行解码。

    例如解码MKV格式的视频文件,就是“MKV->H.264码流->YUV”。

    二:FFmpeg解码流程

    本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

    三:FFmpeg解码函数  

    1. av_register_all():注册所有组件。
    2. avformat_open_input():打开输入视频文件。
    3. avformat_find_stream_info():获取视频文件信息。
    4. avcodec_find_decoder():查找解码器。
    5. avcodec_open2():打开解码器。
    6. av_read_frame():从输入文件读取一帧压缩数据。
    7. avcodec_decode_video2():解码一帧压缩数据。
    8. avcodec_close():关闭解码器。
    9. avformat_close_input():关闭输入视频文件。

    四:FFmpeg解码的数据结构  

    五:FFmpeg数据结构简介 

    AVFormatContext

    封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息

    AVInputFormat

    每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。

    AVStream[2]

    视频文件中每个视频(音频)流对应一个该结构体。

    AVCodecContext

    编码器上下文结构体,保存了视频(音频)编解码相关信息。

    AVCodec

    每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。

    AVPacket

    存储一帧压缩编码数据。

    AVFrame

    存储一帧解码后像素(采样)数据。

    六:FFmpeg数据结构分析

     

     

     

    本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

    七:像素数据转换

    解码后YUV格式的视频像素数据保存在AVFrame的data[0]、data[1]、data[2]中。

    但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素 。

    以亮度 Y 数据为例 , data[0] 中一共包含了linesize[0]* height个数据。

    但是出于优化等方面的考虑,linesize[0]实际上并不等于宽度width,而是一个比宽度大一些的值。

    因此需要使用sws_scale()进行转换。转换后去除了无效数据,width和linesize[0]取值相等。

     

     八:FFmpeg解码

    实现步骤

    1.注册所有组件

    av_register_all();

     2.打开视频输入文件 

    1. //文件路径设置 程序运行当前路径-exe所存在的路径
    2. QString filename = QCoreApplication::applicationDirPath();
    3. qDebug()<<"获取程序运行目录 "<<filename;
    4. //文件名称中文乱码-建议使用英文
    5. QString cinputFilePath = "test.avi"; //本地视频文件放入程序运行目录
    6. //指针开空间
    7. avformat_context = avformat_alloc_context();
    8. //参数一:封装格式上下文->AVFormatContext->包含了视频信息(视频格式、大小等等...)
    9. //参数二:打开文件(入口文件)->url
    10. qDebug()<<"打开"<<videoname<<"视频文件进行播放";
    11. //打开文件把文件详细信息传入avformat_context
    12. //形参代表什么 返回值什么含义 标准C++String类型转C的char*类型
    13. int avformat_open_result = avformat_open_input(&avformat_context,videoname.toStdString().c_str(),NULL,NULL);
    14. if (avformat_open_result != 0)
    15. {
    16. //获取异常信息--打开视频文件失败--具体失败原因
    17. char* error_info = new char[32];
    18. av_strerror(avformat_open_result, error_info, 1024);
    19. qDebug()<<QString("异常信息 %1").arg(error_info);
    20. };

    3.查找视频流信息

    1. //参数一:封装格式上下文->AVFormatContext
    2. //参数二:配置
    3. //返回值:0>=返回OK,否则失败 查找流信息
    4. int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
    5. if (avformat_find_stream_info_result < 0){
    6. //获取失败--没有找到流信息
    7. char* error_info = new char[32];
    8. av_strerror(avformat_find_stream_info_result, error_info, 1024);
    9. qDebug()<<QString("异常信息 %1").arg(error_info);
    10. }

    4.查找解码器 

    1. //第一点:获取当前解码器是属于什么类型解码器->找到了视频流
    2. //音频解码器、视频解码器、字幕解码器等等...
    3. //获取视频解码器流引用
    4. av_stream_index = -1;
    5. //循环遍历流信息
    6. for (int i = 0; i < avformat_context->nb_streams; ++i) {
    7. //循环遍历每一流
    8. //视频流、音频流、字幕流等等... 查找视频流
    9. if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
    10. //找到了
    11. av_stream_index = i;
    12. break;
    13. }
    14. }
    15. if (av_stream_index == -1)
    16. {
    17. qDebug()<<QString("没有找到视频流");
    18. }
    19. //第二点:根据视频流->查找到视频解码器上下文->视频压缩数据
    20. //编解码器上下文---是否有合适的编解码器
    21. avcodec_context = avformat_context->streams[av_stream_index]->codec;
    22. //第三点:根据解码器上下文->获取解码器ID
    23. avcodec = avcodec_find_decoder(avcodec_context->codec_id);
    24. if (avcodec == NULL)
    25. {
    26. qDebug()<<QString("没有找到视频解码器");
    27. }

    5.打开解码器 

    1. int avcodec_open2_result = avcodec_open2(avcodec_context,avcodec,NULL);
    2. if (avcodec_open2_result != 0)
    3. {
    4. char* error_info = new char[32];
    5. av_strerror(avformat_find_stream_info_result, error_info, 1024);
    6. qDebug()<<QString("异常信息 %1").arg(error_info);
    7. }
    8. qDebug()<<"视频详细信息输出";
    9. //此函数自动打印输入或输出的详细信息--退出时候才会有信息显示
    10. av_dump_format(avformat_context, 0, cinputFilePath.toStdString().c_str(), 0);
    11. qDebug()<<"----------------解码准备工作完成-----------------";

    6.循环解码 

    1. //读取帧数据换成到哪里->缓存到packet里面
    2. av_packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    3. //输入->环境一帧数据->缓冲区->类似于一张图
    4. pFramein = av_frame_alloc();
    5. //输出->帧数据->数据格式->RGB
    6. pFrameRGB = av_frame_alloc();
    7. //只有指定了AVFrame的像素格式、画面大小才能真正分配内存
    8. //缓冲区分配内存
    9. pOutbuffer = (uint8_t *)av_malloc(avpicture_get_size(
    10. AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height));
    11. //初始化缓冲区 类似于内存的memset-开辟完清理操作
    12. avpicture_fill((AVPicture *)pFrameRGB, pOutbuffer,
    13. AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height);
    14. //解码的状态类型(0:表示解码完毕,非0:表示正在解码)
    15. // int current_frame_index = 0;
    16. //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
    17. //准备一个视频像素数据格式上下文
    18. //参数一:输入帧数据宽
    19. //参数二:输入帧数据高
    20. //参数三:输入帧数据格式
    21. //参数四:输出帧数据宽
    22. //参数五:输出帧数据高
    23. //参数六:输出帧数据格式->AV_PIX_FMT_RGB32
    24. //参数七:视频像素数据格式转换算法类型
    25. //参数八:字节对齐类型(C/C++里面)->提高读取效率
    26. SwsContext* pSwsContext = sws_getContext(avcodec_context->width,
    27. avcodec_context->height,
    28. avcodec_context->pix_fmt,
    29. avcodec_context->width,
    30. avcodec_context->height,
    31. AV_PIX_FMT_RGB32,
    32. SWS_BICUBIC,NULL,NULL,NULL);
    33. int ret;//解码结果--处理出来的图片
    34. //解码的状态类型(0:表示解码完毕,非0:表示正在解码)--在循环体外进行操作
    35. int current_frame_index = 0;
    36. //线程标志位
    37. while (m_stop == false)
    38. {
    39. //>=0:说明有数据,继续读取 <0:说明读取完毕,结束
    40. //从视频文件上下文中读取包--- 有数据就一直读取
    41. //判断--有数据的话才会一直读取
    42. if (av_read_frame(avformat_context,av_packet) >= 0)
    43. {
    44. //解码什么类型流(视频流、音频流、字幕流等等...)
    45. if (av_packet->stream_index == av_stream_index)
    46. {
    47. //解码一帧视频数据
    48. avcodec_send_packet(avcodec_context, av_packet);
    49. //接收一帧数据->解码一帧 处理出来的图片存储到pFramein
    50. ret = avcodec_receive_frame(avcodec_context,pFramein);
    51. //处理出来的图片是否可行
    52. if (ret == 0)//解码成功
    53. {
    54. //图片的转换的相关操作 输入pFramein 输出pFrameRGB
    55. sws_scale(pSwsContext, (const unsigned char* const*)pFramein->data, pFramein->linesize, 0, avcodec_context->height,
    56. pFrameRGB->data, pFrameRGB->linesize);
    57. QImage *tmpImg = new QImage((uchar *)pOutbuffer, avcodec_context->width,
    58. avcodec_context->height,QImage::Format_RGB32);
    59. QImage image=tmpImg->copy();
    60. qDebug()<<"接收图片信号"<<image;
    61. //解码得到的
    62. //一部分做显示 发送信号(图片)--emit
    63. emit sigGetOneFrame(image);
    64. //一部分做编码
    65. //循环 编码一帧数据
    66. pvideoCode->codeingOneFrame(pFramein);
    67. //遍历每一帧的信息进行打印
    68. current_frame_index++;
    69. //发送信号-emit 解码信息放在窗口中进行显示
    70. emit SendOneData(current_frame_index);
    71. //延时操作 1秒显示25帧--1000/25=40
    72. QThread::msleep(40);
    73. //获取的视频信息
    74. qDebug()<<QString("当前遍历第 %1 帧").arg(current_frame_index);
    75. }
    76. }
    77. }
    78. av_free_packet(av_packet);

    7.关闭所有解码组件 

    1. av_packet_free(&av_packet);
    2. //关闭流
    3. avcodec_close(avcodec_context);
    4. avformat_free_context(avformat_context);

    九:FFMPEG解码-视频播放

    线程简介

    •线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    线程状态

    线程启动和停止 

    •线程启动调用start()函数。

    •RUN函数执行完表示线程退出。

    QThread 使用测试

    如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论! 

    本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

  • 相关阅读:
    Linux Zabbix企业级监控平台+cpolar实现远程访问
    通过 Jenkins 经典 UI 创建一个基本流水线
    智己汽车数据驱动中心PMO高级经理张晶女士受邀为第十三届中国PMO大会演讲嘉宾
    Java代码审计-Filter核心技术
    C 回调函数,接口 函数指针作为函数的参数 函数指针作为结构体成员
    移知丨一个芯片工程师的ADC学习笔记 (一)
    TTS 擂台: 文本转语音模型的自由搏击场
    探索Java面向对象编程的奇妙世界(四)
    大数据Flink(八十六):DML:Group 聚合和Over 聚合
    Dubbo 路由及负载均衡性能优化
  • 原文地址:https://blog.csdn.net/m0_60259116/article/details/127093370