导读

在前面我们介绍了FFmpeg的解封装,并且实现了提取视频文件中的音频流和视频流单独输出,使用ffplay播放验证,
今天我们使用FFmpeg解码视频流,将视频解码为YUV并输出到文件,然后使用ffplay播放YUV图像。

关于YUV的相关知识,之前笔者也有过一些笔记,但是写的比较简单,大家可以网上找找更加详细的资料:
音视频基础知识-YUV图像

关于使用FFmpeg进行视频解码的文章,之前也写过类似的文章《Android使用ffmpeg解码视频为YUV》
但是在这篇文章中有一个错误的点就是写入的YUV的方法不是通用的,对于一些视频解码出来的YUV,按照文章中的方法写入可能会有播放花屏,甚至无法播放的情况。对于这点如果有误人子弟的话,
笔者深感抱歉,在这里说明一下,笔者发表的这些博客仅作为笔记或交流需要,不具备权威性,观点总结仅限于自己的理解,不保证所有的准确性哈。。。

AVFrame介绍

相对于解封装而言,视频解码时我们需要用到一个新的结构体AVFrame。

AVFrame可以说是一个与AVPacket相对应的结构体,既然AVPacket表示的是音视频包解码前或编码后的数据,那么AVFrame就是音视频包解码后或编码前的原始数据包。

AVFrame内部包含了一个视频帧或音频帧所持续播放的时间,播放的时机等时间信息,同时还包含了采样率,采样格式、图片格式、帧类型等相关信息。

在FFmpeg中我们使用av_frame_alloc()分配一个AVFrame,使用av_frame_free释放一个AVFrame,使用函数av_frame_get_buffer为AVFrame内部分配数据缓冲区。

视频解码

回顾之前的解封装的一张图:

视频的解码阶段就是发生在函数av_read_frame之后,如果读取到的资源包是视频类型的则送进解码器进行解码。

我们来看看另外一张图,这张图主要介绍了解码视频过程中用到的一些结构体的功能:

下面简单介绍一下视频解码的两个重要操作步骤:

1、配置解码器

配置解码器这个步骤又可以拆分为四个小步骤:

a、查找解码器
b、分配解码器上下文
c、按照视频流信息配置解码参数到解码器上下文
d、打开解码器

这四个小步骤对于的FFmpeg的API分别是:

// 查找解码器
avcodec_find_decoder 或 avcodec_find_decoder
// 分配解码器上下文
avcodec_alloc_context3
//按照视频流信息配置解码参数到解码器上下文
avcodec_parameters_to_context
//打开解码器
avcodec_open2

2、发送解码包及获取解码YUV数据帧

解码阶段主要用到的两个关键API是avcodec_send_packetavcodec_receive_frame其中avcodec_send_packet表示发送一个视频数据包到解码器,然后使用avcodec_receive_frame接收
解码数据帧(也就是YUV数据)。avcodec_send_packetavcodec_receive_frame并不是一一对应的调用关系,而是一个avcodec_send_packet的调用,可能会对应n个avcodec_receive_frame函数的
调用。因为解码器内部是有缓存和参考帧的,并不是每送进去一个数据包就能解码出一帧数据,可能出现送进去几个数据包,但是暂时没有数据帧解码输出的情况,也可能会出现某个时间点送进去一个数据包,然后会输出n个数据帧的情况。

主要代码如下:

VideoDecoder.h#include <string>class VideoDecoder {public:VideoDecoder();~VideoDecoder();void decode_video(std::string media_path, std::string yuv_path);
};

以下是实现文件:

VideoDecoder.cpp
#include "VideoDecoder.h"
#include <iostream>
extern "C"{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/log.h>
}VideoDecoder::VideoDecoder() {}VideoDecoder::~VideoDecoder() {}void VideoDecoder::decode_video(std::string media_path, std::string yuv_path) {AVFormatContext *avFormatContext = nullptr;AVCodecContext *avCodecContext = nullptr;avFormatContext = avformat_alloc_context();avformat_open_input(&avFormatContext,media_path.c_str(), nullptr,nullptr);av_dump_format(avFormatContext,0,media_path.c_str(),0);int video_index = av_find_best_stream(avFormatContext,AVMEDIA_TYPE_VIDEO,-1,-1, nullptr,0);if(video_index < 0){std::cout << "没有找到视频" << std::endl;}const AVCodec *avCodec = avcodec_find_decoder(avFormatContext->streams[video_index]->codecpar->codec_id);avCodecContext = avcodec_alloc_context3(avCodec);avcodec_parameters_to_context(avCodecContext,avFormatContext->streams[video_index]->codecpar);int ret = avcodec_open2(avCodecContext,avCodec, nullptr);if(ret < 0){std::cout << "解码器打开失败" << std::endl;}FILE *yuv_file = fopen(yuv_path.c_str(),"wb");AVPacket *avPacket = av_packet_alloc();AVFrame *avFrame = av_frame_alloc();while (true){ret = av_read_frame(avFormatContext,avPacket);if(ret < 0){std::cout << "文件读取完毕" << std::endl;break;} else if(video_index == avPacket->stream_index){ret = avcodec_send_packet(avCodecContext,avPacket);if(ret < 0){std::cout << "视频发送解码失败:" << av_err2str(ret) << std::endl;}while (true){ret = avcodec_receive_frame(avCodecContext,avFrame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {std::cout << "avcodec_receive_frame:" << av_err2str(ret) << std::endl;break;} else if (ret < 0) {std::cout << "视频解码失败:" << std::endl;return;} else{std::cout << "写入YUV文件avFrame->linesize[0]:"  << avFrame->linesize[0]  << "avFrame->width:" << avFrame->width << std::endl;std::cout << "avFrame->format:"  << avFrame->format << std::endl;// 播放 ffplay -i YUV文件路径 -pixel_format yuv420p -framerate 25 -video_size 640x480// frame->linesize[1]  对齐的问题// 正确写法  linesize[]代表每行的字节数量,所以每行的偏移是linesize[]// 成员data是个指针数组,每个成员所指向的就是yuv三个分量的实体数据了,成员linesize是指对应于每一行的大小,为什么需要这个变量,是因为在YUV格式和RGB格式时,每行的大小不一定等于图像的宽度//for(int j=0; j<avFrame->height; j++)fwrite(avFrame->data[0] + j * avFrame->linesize[0], 1, avFrame->width, yuv_file);for(int j=0; j<avFrame->height/2; j++)fwrite(avFrame->data[1] + j * avFrame->linesize[1], 1, avFrame->width/2, yuv_file);for(int j=0; j<avFrame->height/2; j++)fwrite(avFrame->data[2] + j * avFrame->linesize[2], 1, avFrame->width/2, yuv_file);// 错误写法 用source.200kbps.766x322_10s.h264测试时可以看出该种方法是错误的// 如果frame.width == avFrame->linesize[0] 则可以用这种方式写入//  写入y分量
//        fwrite(avFrame->data[0], 1, avFrame->width * avFrame->height,  yuv_file);//Y
//        // 写入u分量
//        fwrite(avFrame->data[1], 1, (avFrame->width) *(avFrame->height)/4,yuv_file);//U:宽高均是Y的一半
//        //  写入v分量
//        fwrite(avFrame->data[2], 1, (avFrame->width) *(avFrame->height)/4,yuv_file);//V:宽高均是Y的一半}}}av_packet_unref(avPacket);}fflush(yuv_file);av_packet_free(&avPacket);av_frame_free(&avFrame);if(nullptr != yuv_file) {fclose(yuv_file);yuv_file = nullptr;}
}

对于解码出来的YUV输出文件,我们可以使用ffplay命令来进行播放:

// 其中 640x480 需要替换成自己解码的视频的真实宽高
ffplay -i YUV文件路径 -pixel_format yuv420p -framerate 25 -video_size 640x480

针对导读中提到的YUV非通用写法,笔者在代码中做了简单的注释。更多的资料可以查询关于FFmpeg内存对齐的问题,例如针对图像来说并不是像素对齐而是字节对齐的。

注意

在上面的例子中,视频文件读取完毕之后并没有对编码器内部进行数据冲刷,可能会导致视频的最后几帧丢失的情况,更加规范的写法应该在文件读取完毕之后再次调用函数avcodec_send_packet但是需要传入空的视频数据包,
然后通过循环调用avcodec_receive_frame将解码器中缓存的数据帧全部获取出来。

还有别忘了释放资源。。。

推荐阅读

FFmpeg连载1-开发环境搭建
FFmpeg连载2-分离视频和音频

关注我,一起进步,人生不止coding!!!

FFmpeg连载3-视频解码相关推荐

  1. FFmpeg连载4-音频解码

    导读 前面我们介绍了使用FFmpeg解码视频,今天我们使用FFmpeg解码音频.我们的目标将mp4中的音频文件解码成PCM数据,并输出到本地文件,然后使用ffplay播放验证. 音频的解码过程就是将经 ...

  2. 利用ffmpeg来进行视频解码的完整示例代码

    (转)利用ffmpeg来进行视频解码的完整示例代码(H.264) Decode() { FILE  * inpf; int  nWrite; int  i,p; int  nalLen; unsign ...

  3. FFmpeg连载7-mp3转码aac及AVAudioFifo的使用

    前言 如今以抖音.快手为代表的短视频秀无处不在,比如它们一个很普通的功能就是使用流行音乐替换作为视频的背景音乐.而在视频中音频一般都是以AAC的形成存在,但流行音乐大多以mp3的格式传播, 因此需要完 ...

  4. 嵌入式linux h.264,利用ffmpeg来进行视频解码h.264格式(linux)

    系统环境:ubuntu 10.04 1. 安装解码库(若是windows 平台,下载ffmpeg sdk即可) # apt-get install libavcodec-dev libavformat ...

  5. ffmpeg C语言视频解码

    视频解码生成YUV格式的原始数据 #include <stdio.h> #include <stdlib.h> #include "libavcodec/avcode ...

  6. 利用ffmpeg来进行视频解码的完整示例代码(H.264)

    Decode()     {     FILE * inpf;           int nWrite;     int i,p;     int nalLen;     unsigned char ...

  7. ffmpeg笔记_视频解码

    // TODO 摘抄 #include <stdio.h> #include <stdlib.h> #include <string.h>#include < ...

  8. ffmpeg 将拆分的数据合成一帧_FFmpeg + OpenGLES 实现视频解码播放和视频滤镜

    FFmpeg 开发系列连载: FFmpeg 开发(01):FFmpeg 编译和集成 FFmpeg 开发(02):FFmpeg + ANativeWindow 实现视频解码播放 FFmpeg 开发(03 ...

  9. FFmpeg之硬解码

    导读 前面我们已经使用NDK编译出了FFmpeg并且已经集成到了Android Studio中去,相关文章:NDK21编译ffmpeg5.0.1 众所周知,软解码虽然兼容性一流,但是却非常依赖CPU, ...

最新文章

  1. 《大规模Scrum:More with LeSS》访谈
  2. SQL Server 2005——下一代的数据管理和分析软件[转]
  3. 2018年9月份GitHub上最热门的Python项目
  4. 数据可视化|实验三 分析特征内部数据分布于分散状况
  5. 电路板上的插头怎么拔下来_中国连城 | 接插件在电路板上的作用及它的种类介绍...
  6. 使用js实现思维导图
  7. python环境配置,windows系统,anaconda集成开发环境
  8. MSP430使用__delay_cycles实现延时1ms和1us
  9. 深度学习(十五)基于级联卷积神经网络的人脸特征点定位-CVPR 2013
  10. 明小子注入工具+啊D注入工具+御剑后台扫描工具+中国菜刀一句话木马
  11. 好用的滚动式截图工具picpick
  12. 校园失物招领小程序 开题报告(基于微信小程序毕业设计题目选题课题)
  13. centOS下python用ffmpeg将MP3转换成WAV
  14. problems encountered during text search
  15. 豆瓣最新API-python
  16. 网易新闻详情页排版实现思路
  17. iphone X 屏幕适配
  18. 中兴通信亮相文博会,5G创新变革
  19. 针对严峻的网络安全环境,公司就当如何应对?
  20. RK3399 - Android7.1 调试串口波特率修改

热门文章

  1. 【调剂】江南大学2022年硕士研究生调剂公告(二)
  2. 【零基础新手小白】OD破解基本的认识
  3. 【BIGEMAP一键离线地图服务】
  4. 如何修改word已经批注完成的作者的批注名
  5. 软件测试金字塔,软件质量思考(一)测试金字塔
  6. vue创建项目卡住不动,vue create project卡住不动
  7. Mysql 、oracle 、 zookpper、 redis 默认端口
  8. dokuwiki上传图片(文件资源)失败
  9. 短视频剪辑怎么做?分享几款好用的软件工具给大家
  10. 评测三款最流行的epub阅读器