目录

  1. FFmpeg解码过程流程图和关键的数据结构

  2. mp4通过FFmpeg解码YUV裸视频数据

一、FFmpeg解码过程流程图和关键的数据结构

FFmpeg解码涉及的知识点比较多,很容易被函数和结构体搞定不知所错,我们先从整体上对解码流程有个认知,画了张解码流程图,如下

1.1 解码流程如下

  1. avformat_open_input 打开媒体文件

  2. avformat_find_stream_info 初始化AVFormatContext_

  3. 匹配到视频流的index

  4. avcodec_find_decoder 根据视频流信息的codec_id找到对应的解码器_

  5. avcodec_open2 使用给定的AVCodec初始化AVCodecContext_

  6. 初始化输出文件、解码AVPacket和AVFrame结构体

  7. av_read_frame 开始一帧一帧读取

  8. avcodec_send_packet

  9. avcodec_receive_frame

  10. 格式转换 、分别写入YUV文件

  11. Opengl渲染(本篇不涉及,放到后面单独篇学习实践)

  12. 释放资源

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

1.2 关键函数

下面我们来看下解码流程中的关键函数

1. av_register_all
在3.x或者以前的版本在使用ffmpeg的复用/解复用器或者编解码器之前一定要先调用该函数。但是4.x之后ffmpeg修了了内部实现,该函数可以省略不写。

2. avformat_open_input

attribute_deprecated int av_open_input_file(AVFormatContext **ic_ptr,constchar *filename,AVInputFormat *fmt,int buf_size,AVFormatParameters *ap);

以输入方式打开一个媒体文件,codecs并没有打开,只读取了文件的头信息.

3. avformat_find_stream_info

Read packets of a media file to get stream informationint avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

获取多媒体信息

4. avcodec_find_decoder

Find a registered decoder with a matching codec IDAVCodec *avcodec_find_decoder(enum AVCodecID id);

根据codecID找到一个注册过的解码器

5. avcodec_open2

Initialize the AVCodecContext to use the given AVCodecint avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

使用给定的AVCodec初始化AVCodecContext_

6. av_read_frame

Return the next frame of a stream.
@return 0 if OK, < 0 on error or end of fileint av_read_frame(AVFormatContext *s, AVPacket *pkt);

读取一帧数据,读到的是AVPacket

7. avcodec_send_packet

Supply raw packet data as input to a decoder.
@return 0 on success, otherwise negative error codeint avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

给解码器发送一帧压缩的AVPacket 数据

8. avcodec_receive_frame

Return decoded output data from a decoder.
@return 0 on successint avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

接收解码器解码的一帧AVFrame数据

9. sws_scale

int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],const int srcStride[], int srcSliceY, int srcSliceH,uint8_t *const dst[], const int dstStride[]);

解码后YUV格式的视频像素数据保存在AVFrame的data 0-3中。但这些像素并不是连续存储的,每行有效像素之后存储了一些无效像素,经过该函数处理,去掉无效数据。否则会出现花屏。

10. 资源释放相关函数

    av_packet_unref(packet);sws_freeContext(img_convert_ctx);fclose(pYUVFile);av_frame_free(&pFrameYUV);av_frame_free(&pFrame);avcodec_close(pCodecContext);avformat_close_input(&avFormatContext);

1.3 关键结构体

关键结构体包括AVFormatContext、AVStream、AVCodecContext、AVCodec、AVCodecParameters、AVPacket、AVFrame等

下面4张图片来自雷神

AVFormatCotext和AVInputFormat是和封装格式相关的结构体

AVPacket 与 AVFrame

1.4 补充知识

1. 宏定义define里面的##

宏定义define里面的##可能不太常见,它的含义就是拼接两个字符串,比如
#define Conn(x,y) x##y那么 int  n = Conn(123,456);  结果就是n=123456;

2. 文件的打开方式

File * fp = fopen(info.txt,"wb+")fprintf()
或者fwirtefclose(fp);

关于打开方式的说明如下

  r+ 以可读写方式打开文件,该文件必须存在。rb+ 读写打开一个二进制文件,只允许读写数据。rt+ 读写打开一个文本文件,允许读和写。w 打开只写文件,若文件存在则文件长度0,若文件不存在则建立该文件。w+ 打开可读写文件,若文件存在则文件长度清为零,若文件不存在则建立该文件。a 以附加的方式打开只写文件a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后wb 只写打开或新建一个二进制文件;只允许写数据。wb+ 读写打开或建立一个二进制文件,允许读和写。wt+ 读写打开或着建立一个文本文件;允许读写。at+ 读写打开一个文本文件,允许读或在文本末追加数据。ab+ 读写打开一个二进制文件,允许读或在文件末追加数据。

3. YUV数据类型

输出解码前的h264码流、输出解码后的YUV信息
使用Elecard StreamEye Tools查看输出的h264数据

视频显示的流程,就是将像素数据“画”在屏幕上的过程。
例如显示YUV,就是将YUV“画”在系统的窗口中。

YUV 4:4:4采样,每一个Y对应一组UV分量。
YUV 4:2:2采样,每两个Y共用一组UV分量。
YUV 4:2:0采样,每四个Y共用一组UV分量。YUV420P,Y,U,V三个分量都是平面格式,分为I420和YV12。I420格式和YV12格式的不同处在U平面和V平面的位置不同。在I420格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV);但YV12则是相反(即:YVU)。
YUV420SP, Y分量平面格式,UV打包格式, 即NV12。NV12与NV21类似,U 和 V 交错排列,不同在于UV顺序。I420: YYYYYYYY UU VV    =>YUV420P (最常见的)
YV12: YYYYYYYY VV UU    =>YUV420P
NV12: YYYYYYYY UVUV     =>YUV420SP
NV21: YYYYYYYY VUVU     =>YUV420SP

二、mp4通过FFmpeg解码成YUV裸数据

通过上一小节,我们了解了FFmpeg解码流程和关键的结构体,这一小节我们来实践。

具体步骤说明和代码实现如下:

#include <jni.h>
#include <string>extern "C" {
#include "include/libavcodec/avcodec.h"
#include "include/libavformat/avformat.h"
#include "include/log.h"
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>}extern "C"
JNIEXPORT jint JNICALL
Java_android_spport_mylibrary2_Demo_decodeVideo(JNIEnv *env, jobject thiz, jstring inputPath,jstring outPath) {//申请avFormatContext空间,记得要释放AVFormatContext *avFormatContext = avformat_alloc_context();const char *url = env->GetStringUTFChars(inputPath, 0);//1. 打开媒体文件int reuslt = avformat_open_input(&avFormatContext, url, NULL, NULL);if (reuslt != 0) {LOGE("open input error url=%s, result=%d", url, reuslt);return -1;}//2.读取媒体文件信息,给avFormatContext赋值if (avformat_find_stream_info(avFormatContext, NULL) < 0) {LOGE("find stream error");return -1;}//3. 匹配到视频流的indexint videoIndex = -1;for (int i = 0; i < avFormatContext->nb_streams; i++) {AVMediaType codecType = avFormatContext->streams[i]->codecpar->codec_type;LOGI("avcodec type %d", codecType);if (AVMEDIA_TYPE_VIDEO == codecType) {videoIndex = i;break;}}if (videoIndex == -1) {LOGE("not find a video stream");return -1;}AVCodecParameters *pCodecParameters = avFormatContext->streams[videoIndex]->codecpar;//4. 根据视频流信息的codec_id找到对应的解码器AVCodec *pCodec = avcodec_find_decoder(pCodecParameters->codec_id);if (pCodec == NULL) {LOGE("Couldn`t find Codec");return -1;}AVCodecContext *pCodecContext = avFormatContext->streams[videoIndex]->codec;//5.使用给定的AVCodec初始化AVCodecContextint openResult = avcodec_open2(pCodecContext, pCodec, NULL);if (openResult < 0) {LOGE("avcodec open2 result %d", openResult);return -1;}const char *outPathStr = env->GetStringUTFChars(outPath, NULL);//6. 初始化输出文件、解码AVPacket和AVFrame结构体//新建一个二进制文件,已存在的文件将内容清空,允许读写FILE *pYUVFile = fopen(outPathStr, "wb+");if (pYUVFile == NULL) {LOGE(" fopen outPut file error");return -1;}auto *packet = (AVPacket *) av_malloc(sizeof(AVPacket));//avcodec_receive_frame时作为参数,获取到frame,获取到的frame有些可能是错误的要过滤掉,否则相应帧可能出现绿屏AVFrame *pFrame = av_frame_alloc();//作为yuv输出的frame承载者,会进行缩放和过滤出错的帧,YUV相应的数据也是从该对象中读取AVFrame *pFrameYUV = av_frame_alloc();//out_buffer中数据用于渲染的,且格式为YUV420Puint8_t *out_buffer = (unsigned char *) av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecContext->width,pCodecContext->height, 1));av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,AV_PIX_FMT_YUV420P, pCodecContext->width, pCodecContext->height, 1);// 由于解码出来的帧格式不一定是YUV420P的,在渲染之前需要进行格式转换struct SwsContext *img_convert_ctx = sws_getContext(pCodecContext->width, pCodecContext->height,pCodecContext->pix_fmt,pCodecContext->width, pCodecContext->height,AV_PIX_FMT_YUV420P,SWS_BICUBIC, NULL, NULL, NULL);int readPackCount = -1;int frame_cnt = 0;clock_t startTime = clock();//7. 开始一帧一帧读取while ((readPackCount = av_read_frame(avFormatContext, packet) >= 0)) {LOGI(" read fame count is %d", readPackCount);if (packet->stream_index == videoIndex) {//8. send AVPacketint sendPacket = avcodec_send_packet(pCodecContext, packet);//return 0 on success, otherwise negative error code:if (sendPacket != 0) {LOGE("avodec send packet error %d", sendPacket);continue;}//9. receive frame// 0:  success, a frame was returnedint receiveFrame = avcodec_receive_frame(pCodecContext, pFrame);if (receiveFrame != 0) {//如果接收到的fame不等于0,忽略这次receiver否则会出现绿屏帧LOGE("avcodec_receive_frame error %d", receiveFrame);continue;}//10. 格式转换sws_scale(img_convert_ctx, (const uint8_t *const *) pFrame->data, pFrame->linesize,0, pCodecContext->height,pFrameYUV->data, pFrameYUV->linesize);//11. 分别写入YUV数据int y_size = pCodecParameters->width * pCodecParameters->height;//YUV420pfwrite(pFrameYUV->data[0], 1, y_size, pYUVFile);//Yfwrite(pFrameYUV->data[1], 1, y_size / 4, pYUVFile);//Ufwrite(pFrameYUV->data[2], 1, y_size / 4, pYUVFile);//V//输出I、P、B帧信息char pictypeStr[10] = {0};switch (pFrame->pict_type) {case AV_PICTURE_TYPE_I: {sprintf(pictypeStr, "I");break;}case AV_PICTURE_TYPE_P: {sprintf(pictypeStr, "P");break;}case AV_PICTURE_TYPE_B: {sprintf(pictypeStr, "B");break;}}LOGI("Frame index %5d. Tpye %s", frame_cnt, pictypeStr);frame_cnt++;}}LOGI("frame count is %d", frame_cnt);clock_t endTime = clock();//long类型用%ld输出LOGI("decode video use Time %ld", (endTime - startTime));//12.释放相关资源//释放packetav_packet_unref(packet);sws_freeContext(img_convert_ctx);fclose(pYUVFile);av_frame_free(&pFrameYUV);av_frame_free(&pFrame);avcodec_close(pCodecContext);avformat_close_input(&avFormatContext);return 0;}
}

解码后的数据使用ffplayer进行播放。注意参数设置, 比如格式和分辨率等

eg:
ffplay /Users/yabin/Desktop/tmp/ffmpeg/output8.yuv -pix_fmt yuv420p -s 784x480

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

音视频开发(二十七):基于FFmpeg实现简单的视频解码器相关推荐

  1. 音视频开发之旅(34) - 基于FFmpeg实现简单的视频解码器

    目录 FFmpeg解码过程流程图和关键的数据结构 mp4通过FFmpeg解码YUV裸视频数据 遇到的问题 资料 收获 一.FFmpeg解码过程流程图和关键的数据结构 FFmpeg解码涉及的知识点比较多 ...

  2. 音视频开发系列(62)基于FFmpeg实现简单的视频解码器

    一.FFmpeg解码过程流程图和关键的数据结构 FFmpeg解码涉及的知识点比较多,很容易被函数和结构体搞定不知所错,我们先从整体上对解码流程有个认知,画了张解码流程图,如下 1.1 解码流程如下 a ...

  3. 基于FFmpeg实现简单的视频解码器

    一.FFmpeg解码过程流程图和关键的数据结构 FFmpeg解码涉及的知识点比较多,很容易被函数和结构体搞定不知所错,我们先从整体上对解码流程有个认知,画了张解码流程图,如下 本文福利, 免费领取C+ ...

  4. Android音视频学习系列(十) — 基于FFmpeg + OpenSL ES实现音频万能播放器

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  5. Android 音视频开发(二):使用 AudioRecord 采集音频PCM并保存到文件(学习笔记)

    关于 AudioRecord Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风 ...

  6. Android 音视频开发(二) -- Camera1 实现预览、拍照功能

    音视频 系列文章 Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍 ...

  7. 实时音视频开发理论必备:如何省流量?视频高度压缩背后的预测技术

    本文引用了"拍乐云Pano"的"深入浅出理解视频编解码技术"和"揭秘视频千倍压缩背后的技术原理之本文引用了"拍乐云Pano"的&q ...

  8. android全平台基于ffmpeg解码本地MP4视频推流到RTMP服务器

    音视频实践学习 android全平台编译ffmpeg以及x264与fdk-aac实践 ubuntu下使用nginx和nginx-rtmp-module配置直播推流服务器 android全平台编译ffm ...

  9. SpringBoot入门建站全系列(二十七)WebSocket做简单的聊天室

    SpringBoot入门建站全系列(二十七)WebSocket做简单的聊天室 一.概述 WebSocket 是一种网络通信协议.RFC6455 定义了它的通信标准. WebSocket 是 HTML5 ...

最新文章

  1. ORA-**,oracle 12c操作问题
  2. mysql导入导出数据库
  3. 数据结构最短路径例题_编程小白暑期进阶笔记45-C语言数据结构与算法最短路径和dijkstra算法...
  4. 自考总结--2020年10月份
  5. Dynamics CRM2016 Web API之更新记录
  6. java新建对象校验_验证某个对象是否是一个mock对象或者一个spy对象
  7. matlab 读取照片imread,利用matlab读取图像
  8. 1.4 编程基础之逻辑表达式与条件分支 16 三角形判断
  9. winform 中show()函数和showdialog()函数区别
  10. SolidWorks转3DMAX
  11. 【重点】Batch Normalization的诅咒
  12. Tesler去世丨你逃不过复制粘贴,同样也逃不过Tesler定律
  13. vtkdelaunay3d的参数设置_VTK 渲染体数据并加方位标注
  14. HNU--计算机网络实验2
  15. CSDN是怎么实现用户签到,统计签到次数,连续签到天数等功能微服务的
  16. 输出1-100以内的所有质数
  17. curl采集 根据关键词 获取雅虎竞价排名
  18. PID控制器(比例-积分-微分控制器)- I
  19. Charles(青花瓷)抓包教程
  20. 联想+A916+原版官方稳定精简ROM

热门文章

  1. 学习之道阅读思维导图
  2. 六类挂单一键取消脚本
  3. docker入门之Docker Engine
  4. 若依ajax返回数据,Ajax
  5. 前端和后端哪个累,前端入职之后真的很痛苦吗
  6. JDK1.7 sun.net.ftp.FtpClient
  7. single-spa 集成 vue3.0 生产篇
  8. 什么是sql注入,如何防止sql注入
  9. 【C语言】字符串加密解密,字符串左旋,杨氏矩阵,删除字符串中出现次数最少的字符
  10. 有了独自开,我们离自己开发一套系统还会远吗